﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    public class CustomMarkdownSupport
    {
        /// <summary>
        /// 在使用 MarkDown Sharp 转换后，再对所有h1进行处理，自动生成jQuery函数，这样就可以支持点击标题显示、隐藏文本了。
        /// </summary>
        /// <param name="htmlString">传入 Markdown Sharp 转换后的 Html 文本。</param>
        /// <returns></returns>
        public static string AppendProcessor(string htmlString, string directoryMark = "./",
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollapse = MainWindow.HtmlHeadersCollapseType.No,
            bool fillBlankOn = true, bool compilePageMenu = false, string sourceFilePath = null)
        {
            if (string.IsNullOrEmpty(htmlString)) return "";

            //载入jquery库。这东西无论如何都应载入，且只应载入一次。
            StringBuilder javascriptBuilder = new StringBuilder();
            //jQueryStringBuilder.Append("<script src=\"" + directoryMark + "json2.js\"></script>");
            javascriptBuilder.Append("<script src=\"" + directoryMark + "jquery-1.7.0.min.js\"></script>" +
                    //"<script src=\"" + directoryMark + "jquery.jqprint-0.3.js\"></script>" +
                    (compilePageMenu ? "<script src=\"" + directoryMark + "menu_light.js\"></script>" : "") +
                    "<script language=\"javascript\">" +
                        "function print() {" +
                           "$(\"#printArea\").jqprint({" +
                                   "importCSS: true" +
                           "});" +
                         "}" +
                    "</script>");

            StringBuilder sbMenuText = new StringBuilder();

            if (fillBlankOn)
            {
                #region 添加填空模式的js代码

                javascriptBuilder.Append("<script>");
                javascriptBuilder.Append("$(document).ready(function(){$('p>code').css('color', \"transparent\");});");
                javascriptBuilder.Append("$(document).ready(function(){$('li>code').css('color', \"transparent\");});");
                javascriptBuilder.Append("$(document).ready(function(){$('tr>code').css('color', \"transparent\");});");
                javascriptBuilder.Append("$(document).ready(function(){$('.panel>code').css('color', \"transparent\");});");//注意：不能用“.panel code”。
                javascriptBuilder.Append("$(document).ready(function() {");
                javascriptBuilder.Append("$('p').click(function() {");
                javascriptBuilder.Append("var mycss = document.getElementById('themeLink');");
                javascriptBuilder.Append("if (mycss.getAttribute('href').indexOf('lesson_dark.css')>0)");
                javascriptBuilder.Append("{");
                javascriptBuilder.Append("$(this).find('code').css('color', '#213EFF');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("else {");
                javascriptBuilder.Append("$(this).find('code').css('color', '#00971B');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("$(document).ready(function() {");
                javascriptBuilder.Append("$('li').click(function() {");
                javascriptBuilder.Append("var mycss = document.getElementById('themeLink');");
                javascriptBuilder.Append("if (mycss.getAttribute('href').indexOf('lesson_dark.css')>0)");
                javascriptBuilder.Append("{");
                javascriptBuilder.Append("$(this).find('code').css('color', '#213EFF');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("else {");
                javascriptBuilder.Append("$(this).find('code').css('color', '#00971B');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("$(document).ready(function() {");
                javascriptBuilder.Append("$('tr').click(function() {");
                javascriptBuilder.Append("var mycss = document.getElementById('themeLink');");
                javascriptBuilder.Append("if (mycss.getAttribute('href').indexOf('lesson_dark.css')>0)");
                javascriptBuilder.Append("{");
                javascriptBuilder.Append("$(this).find('code').css('color', '#213EFF');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("else {");
                javascriptBuilder.Append("$(this).find('code').css('color', '#00971B');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("$(document).ready(function() {");
                javascriptBuilder.Append("$('.panel').click(function() {");
                javascriptBuilder.Append("var mycss = document.getElementById('themeLink');");
                javascriptBuilder.Append("if (mycss.getAttribute('href').indexOf('lesson_dark.css')>0)");
                javascriptBuilder.Append("{");
                javascriptBuilder.Append("$(this).find('code').css('color', '#213EFF');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("else {");
                javascriptBuilder.Append("$(this).find('code').css('color', '#00971B');");
                javascriptBuilder.Append("}");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("});");
                javascriptBuilder.Append("</script>");
                #endregion
            }

            #region 解决相对路径的URL转义，有Bug。

            //Regex aLinkHref = new Regex(@"(?<=(<[Aa] {1,}.*[hH][rR][eE][fF]=[""'])).*(?=[""'])");
            //var matchesOfHrefs = aLinkHref.Matches(htmlString);
            //for (int i = matchesOfHrefs.Count - 1; i >= 0; i--)
            //{
            //    htmlString = htmlString.Substring(0, matchesOfHrefs[i].Index) +
            //        UrlEncode(matchesOfHrefs[i].Value) +
            //        htmlString.Substring(matchesOfHrefs[i].Index + matchesOfHrefs[i].Length);
            //}

            #endregion

            #region 新版的H1~H6六级标题折叠的代码

            if (Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Auto ||
                Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Manual)
            {
                List<HtmlHeaderInfo> infos = new List<HtmlHeaderInfo>();
                var regexOfHeaders = new Regex(@"\<[hH][123456] ?.*\>.*\<\/[hH][123456]\>");
                var matches = regexOfHeaders.Matches(htmlString);
                foreach (Match match in matches)
                {
                    if (match != null && match.Success)
                    {
                        string header;
                        int level = GetHtmlHeaderLevel(match.Value, out header);
                        var info = new HtmlHeaderInfo()
                        {
                            Level = level,
                            Header = header,
                            Match = match,
                        };
                        infos.Add(info);
                    }
                }

                for (int i = 0; i < infos.Count; i++)
                {
                    var info = infos[i];
                    for (int j = i - 1; j >= 0; j--)
                    {
                        var preInfo = infos[j];
                        if (preInfo.IsClosed == false && info.Level <= preInfo.Level)
                        {
                            info.PreviewHeaderPanelCloseDivsMark += "</div>";
                            preInfo.IsClosed = true;
                        }
                    }

                    var panelIndex = i + 1;
                    info.JavaScriptText = "<script>$(document).ready(function(){"
                        + "$(\"#title" + panelIndex + "\").click(function(){"
                        + "$(\"#panel" + panelIndex + "\").toggle();"
                        + "if($(\"#panel" + panelIndex + "\").is(\":hidden\")){"
                        + "$(\"#header_span" + panelIndex + "\").html('◆ ');}else{"
                        + "$(\"#header_span" + panelIndex + "\").html('◇ ');}"
                        + "});});</script>";
                    var autoHideText = "";
                    if (Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Auto)
                    {
                        autoHideText = " style='display: none;'";
                    }

                    var hideMarkText = (Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Manual ? "◇" : "◆");
                    info.Html = $"<h{info.Level} class='title' id='title{panelIndex}' title='点击展开/折叠'><span id='header_span{panelIndex}' class='header_span'>{hideMarkText} </span>{info.Header}</h{info.Level}>";
                    info.NewText = info.PreviewHeaderPanelCloseDivsMark + info.Html + info.JavaScriptText + $"<div class='panel_title_h{info.Level}' id='panel{panelIndex}'{autoHideText}>";
                }

                if (compilePageMenu)
                {
                    for (int i = infos.Count - 1; i >= 0; i--)
                    {
                        //<span id="" class="anchor"></span>
                        var gotoTopLinkText = "";
                        var info = infos[i];
                        if (i > 0 && compilePageMenu && info.Level == 1)
                        {
                            gotoTopLinkText = "<p class=\"back_to_top_link\"><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>";
                            //其实只需要使用 <a href="#">返回顶部</a> 就可以了，因为当 href 指向 # 时，表示指向网页自身。
                        }

                        var anchorIdText = Guid.NewGuid().ToString();
                        var anchorText = $"<span id=\"{anchorIdText}\" class=\"anchor\"></span>";
                        htmlString = htmlString.Substring(0, info.Match.Index) + gotoTopLinkText + anchorText + info.NewText + htmlString.Substring(info.Match.Index + info.Match.Length);
                        var prefixSpace = (info.Level == 1 ? "" : "　");
                        sbMenuText.Insert(0, $"<p>{prefixSpace}<a href=\"#{anchorIdText}\">{info.Header}</a></p>");
                        //{new string('　', Math.Max(0, info.Level - 1))}加上会有缩进效果，但右边栏宽度不够。
                    }
                }
                else
                {
                    for (int i = infos.Count - 1; i >= 0; i--)
                    {
                        var info = infos[i];
                        htmlString = htmlString.Substring(0, info.Match.Index) + info.NewText + htmlString.Substring(info.Match.Index + info.Match.Length);
                    }
                }

                for (int i = 0; i < infos.Count; i++)
                {
                    if (infos[i].IsClosed == false) htmlString += "</div>";
                }

                htmlString += "<script>$(document).ready(function() {"  //折叠全部标题。
                           + "$('#file_header').click(function() {"
                           + "$('.panel_title_h1,.panel_title_h2,.panel_title_h3,.panel_title_h4,.panel_title_h5,.panel_title_h6').hide();});"
                           + "});</script>"
                           + "<script>$(document).ready(function() {"
                           + "$('#file_header').dblclick(function() {"
                           + "$('.panel_title_h1,.panel_title_h2,.panel_title_h3,.panel_title_h4,.panel_title_h5,.panel_title_h6').show();});"
                           + "});</script>";
            }

            var menuHtml = "";
            if (sbMenuText.Length > 0)
            {
                menuHtml = "<div id=\"left_menu\">"
                             + "<div class=\"left_menu_content\">"
                             + sbMenuText.ToString()
                             + "<p><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>"
                             //下面这行是编译整个工作区时才应该添加的“回到目录页”链接
                             + (string.IsNullOrWhiteSpace(sourceFilePath) == false ? ("<hr/><p>" + BuildIndexLink(sourceFilePath) + "</p>") : "")
                             + "</div>"
                             + "<strong class=\"left_menu_title\"><span> </span></strong>"
                             + "</div>";

            }

            return javascriptBuilder + menuHtml + htmlString;
            #endregion

            #region 旧版的H1折叠代码
            //List<string> blockStrings = new List<string>();

            //int index1 = 0;
            //int index2 = htmlString.IndexOf("<h1");

            //while (index2 >= 0)
            //{
            //    blockStrings.Add(htmlString.Substring(index1, index2 - index1));
            //    index1 = index2;

            //    index2 = htmlString.IndexOf("<h1", index2 + 1);
            //}

            //if (index1 >= 0)
            //    blockStrings.Add(htmlString.Substring(index1));

            //if (blockStrings.Count > 0)
            //{
            //    #region 添加折叠一级标题的js代码

            //    StringBuilder sBuilder = new StringBuilder();
            //    int panelCount = 0;
            //    if (blockStrings.Count > 1)//第一个成员不是以<H1开头的。
            //    {
            //        javascriptBuilder.Append("<script>");

            //        for (int i = 1; i <= blockStrings.Count; i++)
            //        {
            //            String s = blockStrings[i - 1];

            //            if (s.StartsWith("<h1") == false)
            //            {
            //                sBuilder.Append(s);
            //                continue;
            //            }

            //            int indexOfH1End = s.IndexOf("</h1>");
            //            if (indexOfH1End < 0)
            //            {
            //                sBuilder.Append(s);
            //                continue;
            //            }

            //            sBuilder.Append("<h1 ");
            //            sBuilder.Append("id=\"title" + i + "\"");
            //            sBuilder.Append(s.Substring(3, indexOfH1End + 2));
            //            sBuilder.Append("<div ");
            //            sBuilder.Append("id=\"panel" + i + "\"" + (collapseH1 ? " style=\"display: none;\"" : "") + ">");
            //            sBuilder.Append(s.Substring(indexOfH1End + 5));
            //            sBuilder.Append("</div>");

            //            javascriptBuilder.Append("$(document).ready(function(){"
            //                    + "$(\"#title" + i + "\").click(function(){"
            //                    + "$(\"#panel" + i + "\").toggle();});});");

            //            //在页面加载后再隐藏不好，不如直接就不显示。
            //            //if (collapseH1)
            //            //{
            //            //    javascriptBuilder.Append("$(document).ready(function(){"
            //            //            + "$(\"#panel" + i + "\").toggle();});");
            //            //}

            //            panelCount++;
            //        }
            //    }
            //    #endregion

            //    if (panelCount > 0)
            //    {
            //        return javascriptBuilder + "</script>" + sBuilder.ToString();
            //    }

            //    return javascriptBuilder + htmlString;
            //}
            //else
            //    return javascriptBuilder + htmlString;
            #endregion
        }

        /// <summary>
        /// 解决 URL 中特殊字符的转义问题。
        /// 1. +        URL 中 + 号表示空格                      %2B
        /// 2. 空格     URL中的空格可以用 + 号或者编码           %20
        /// 3. /        分隔目录和子目录                         %2F
        /// 4. ?        分隔实际的 URL 和参数                    %3F
        /// 5. %        指定特殊字符                             %25
        /// 6. #        表示书签                                 %23 
        /// 7. &        URL 中指定的参数间的分隔符               %26
        /// 8. =        URL 中指定参数的值                       %3D
        /// </summary>
        /// <param name="value">待转义的相对路径。</param>
        /// <returns>转义后的相对路径。</returns>
        public static string UrlEncode(string value)
        {
            var pieces = value.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach (var s in pieces)
            {
                // % 必须第一个替换。
                sb.Append(s.Replace("%", "%25").Replace("+", "%2B").Replace(" ", "%20").Replace("?", "%3F")
                    .Replace("#", "%23").Replace("&", "%26").Replace("=", "%3D") + "/");
            }

            var result = sb.ToString();
            if (value.StartsWith("/") || value.StartsWith("\\"))
            {
                result = "/" + result;
            }

            if (value.EndsWith("/") || value.EndsWith("\\"))
            {
                result += "/";
            }
            else
            {
                if (result.EndsWith("/") || result.EndsWith("\\"))
                {
                    result = result.Substring(0, result.Length - 1);
                }
            }

            return result;
        }

        /// <summary>
        /// 取 Html 标题的层级，并传出去除标记符之外的文本。
        /// </summary>
        /// <param name="htmlHeaderSpan">形如：“<h1>abc</h1>”这样的文本。</param>
        /// <param name="headerText">用以返回此级标题的内容文本（即 Html 首尾标签之间的文本）。</param>
        /// <returns></returns>
        private static int GetHtmlHeaderLevel(string htmlHeaderSpan, out string headerText)
        {
            if (string.IsNullOrWhiteSpace(htmlHeaderSpan))
            {
                headerText = "";
                return 0;
            }

            var reg = new Regex(@"\>.*\</");
            Match match = reg.Match(htmlHeaderSpan);
            if (match != null && match.Success)
            {
                headerText = match.Value.Substring(1, match.Length - 3);
            }
            else
            {
                headerText = "";
            }

            if (htmlHeaderSpan.StartsWith("<h1>") || htmlHeaderSpan.StartsWith("<H1>")) return 1;
            if (htmlHeaderSpan.StartsWith("<h2>") || htmlHeaderSpan.StartsWith("<H2>")) return 2;
            if (htmlHeaderSpan.StartsWith("<h3>") || htmlHeaderSpan.StartsWith("<H3>")) return 3;
            if (htmlHeaderSpan.StartsWith("<h4>") || htmlHeaderSpan.StartsWith("<H4>")) return 4;
            if (htmlHeaderSpan.StartsWith("<h5>") || htmlHeaderSpan.StartsWith("<H5>")) return 5;
            if (htmlHeaderSpan.StartsWith("<h6>") || htmlHeaderSpan.StartsWith("<H6>")) return 6;

            return 0;
        }

        /// <summary>
        /// 取任务列表项的标记文本（头文本）。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns></returns>
        internal static string GetHeaderOfTaskListItem(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return "";

            var tmp = lineText.Replace(" ", "").Replace("－", "-").Replace("　", "").Replace("\t", "");//括弧间允许空白字符存在，所以不用TrimStart()。
            if (tmp.ToLower().StartsWith("[-]") || tmp.ToLower().StartsWith("[%]") ||
                tmp.ToLower().StartsWith("[+]") || tmp.ToLower().StartsWith("[#]"))
            {
                if (tmp.Length >= 4)
                {
                    if (tmp[3] == '(')
                    {
                        if (tmp.Substring(4).Contains(')'))
                        {
                            //[]()形如这样是文字链接。
                            return "";
                        }
                    }
                }

                return tmp.Substring(0, 3) + " ";
            }
            return "";
        }

        /// <summary>
        /// 取六级标题的正则表达式。
        /// </summary>
        /// <param name="level"></param>
        /// <returns></returns>
        public static string GetTieleRegByLevel(int level)
        {
            switch (level)
            {
                case 1: return "^[ 　]{0,3}[#＃][^#＃]";
                case 2: return "^[ 　]{0,3}[#＃]{2}[^#＃]";
                case 3: return "^[ 　]{0,3}[#＃]{3}[^#＃]";
                case 4: return "^[ 　]{0,3}[#＃]{4}[^#＃]";
                case 5: return "^[ 　]{0,3}[#＃]{5}[^#＃]";
                case 6: return "^[ 　]{0,3}[#＃]{6}[^#＃]";
                default: return "";
            }
        }

        /// <summary>
        /// 判断此行文本是否“region”区域的分割线行。格式是：... region xxx ...。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsRegionSplitter(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex reg = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}.{0,}?[.。]{3,}[ 　\t]{0,}$");
            var match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断指定的文本是不是 Header 标题。格式是：“###xxx”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsHeaderLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex reg = new Regex(@"^[ 　]{0,3}[#＃].*$");
            var match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否 TODO Comment。格式是：“; TODO: xxx”。
        /// </summary>
        /// <param name="lineText"></param>
        /// <returns></returns>
        public static bool IsTodoCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；]([tTＴｔ][oOｏＯ][dDｄＤ][oOｏＯ])?[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否 TODO Comment。格式是：“; TODO: xxx”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="todoCommentMark">用以传出 TODO Comment 的标记文本。</param>
        /// <param name="newTodoCommentTail">用以传出 TODO Comment 的内容文本（除标记以外的、有意义的文本）。</param>
        public static bool IsTodoCommentLine(string lineText, out string todoCommentMark, out string newTodoCommentTail)
        {
            newTodoCommentTail = lineText;
            todoCommentMark = "";
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；]([tTＴｔ][oOｏＯ][dDｄＤ][oOｏＯ])?[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                todoCommentMark = match.Value;
                newTodoCommentTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否编译菜单链接的指示标记文本行。格式是：“; [Menu]: xxx”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="newTodoCommentTail">用以传出 [Menu]: 后的内容文本（除标记以外的、可能有意义的注释文本）。</param>
        public static bool IsMenuMarkLine(string lineText, out string newMenuMarkTail)
        {
            newMenuMarkTail = lineText;
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；](\[?(([mMｍＭ][eEｅＥ][nNｎＮ][uUｕＵ])|(菜单))\]?)[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                newMenuMarkTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否用于指示当前文档演示模式的文本行。格式是：“；PM：xxx”。
        /// </summary>
        /// <param name="lineText"></param>
        /// <param name="newPresentationTypeTail"></param>
        /// <returns></returns>
        private static bool IsPresentationTypeLine(string lineText, out string newPresentationTypeTail)
        {
            newPresentationTypeTail = lineText;
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；](([pPｐＰ][mMｍＭ])|(演示(模式)?))[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                newPresentationTypeTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行是否被嵌入到 引用块 中的 Header 标题。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="match">正则匹配对象。</param>
        public static bool IsHeaderInBlockQuote(string lineText, out Match match)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                match = null;
                return false;
            }

            Regex reg = new Regex(@"^[ 　]{0,3}[>〉》][>〉》 　][#＃]{1,6}");
            match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            match = null;
            return false;
        }

        /// <summary>
        /// 取任务列表项的内容文本（即形如：“[-]xxx”中的“xxx”部分。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        internal static string GetContentOfTaskListItem(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return "";

            var tmp = lineText.Replace(" ", "").Replace("－", "-").Replace("　", "").Replace("\t", "");//括弧间允许空白字符存在，所以不用TrimStart()。
            if (tmp.ToLower().StartsWith("[-]") || tmp.ToLower().StartsWith("[%]") ||
                tmp.ToLower().StartsWith("[+]") || tmp.ToLower().StartsWith("[#]"))
            {
                if (tmp.Length >= 4)
                {
                    if (tmp[3] == '(')
                    {
                        if (tmp.Substring(4).Contains(')'))
                        {
                            //[]()形如这样是文字链接。
                            return "";
                        }
                    }
                }

                return tmp.Substring(3).TrimStart(new char[] { ' ' });
            }
            return "";
        }

        /// <summary>
        /// 预处理markdown文本，这样可以支持表格、块引用预处理等。
        ///
        /// ｜表格标题｜ ｜列标题1｜列标题2｜列标题3｜
        /// ｜：－－－－：｜－－：｜：－－｜ 
        /// ｜列1、行1｜列2、行1｜列3、行1｜
        /// ｜列1、行2｜列2、行2｜列3、行3｜
        /// </summary>
        /// <param name="markdownText">待转换的源 Markdown 文本。</param>
        /// <param name="separatorText">用于分割文本行的分割符。这是考虑文本文件可能来自于不同的操作系统。</param>
        /// <param name="startH1Number">用以表示H1应从什么值开始编号。这是为跨文件接续编号准备的。</param>
        /// <param name="forbiddenAutoNumber">强行禁止自动编号。</param>
        /// <returns></returns>
        public static String PreProcessor(string markdownText, int startH1Number, bool forbiddenAutoNumber, char[] separatorText)
        {
            if (markdownText == null || markdownText.Length == 0)
            {
                return markdownText;
            }

            markdownText = Question.ConvertQuestionsToHtml(markdownText);//先将试题文本转换为html标签。

            String[] lineStrings = markdownText.Split(separatorText, StringSplitOptions.None);

            #region 格式化引用块
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < lineStrings.Length; i++)
            {
                var text = lineStrings[i];
                int markIndex = -1;
                bool isBlockQuote = true;
                if (string.IsNullOrEmpty(text))
                {
                    isBlockQuote = false;
                }
                else
                {
                    for (int j = 0; j < text.Length; j++)
                    {
                        var c = text[j];
                        if (c == ' ' || c == '　') continue;
                        else if (c == '>' || c == '〉' || c == '》')
                        {
                            markIndex = j;
                            continue;
                        }

                        isBlockQuote = false;
                        break;
                    }
                }

                if (isBlockQuote && markIndex >= 0 && markIndex <= 3)
                {
                    var ts = lineStrings[i].TrimStart(new char[] { '\t', ' ', '　' });
                    if (ts.StartsWith("》 ") || ts.StartsWith("》　") || ts.StartsWith("〉 ") || ts.StartsWith("〉　"))
                    {
                        lineStrings[i] = "> " + ts.Substring(2);
                    }
                    else if (ts.StartsWith("》") || ts.StartsWith("〉") || ts.StartsWith(">"))
                    {
                        lineStrings[i] = "> " + ts.Substring(1);
                    }
                }
            }
            #endregion

            markdownText = sb.ToString();

            #region 格式化表格
            List<TableLinesInfo> tableSourcesList = new List<TableLinesInfo>();
            TableLinesInfo tlInfo = null;

            for (int i = 0; i < lineStrings.Length; i++)
            {
                String lineString = lineStrings[i];

                //表格行第一字符可以不是｜或|，但必须是非空字符。
                if (IsTableRow(lineString))
                {
                    if (lineString.StartsWith("|") == false && lineString.StartsWith("｜") == false)
                    {
                        lineString = "|" + lineString;
                    }

                    if (lineString.EndsWith("|") == false && lineString.EndsWith("｜") == false)
                    {
                        lineString = lineString + "|";
                    }
                }

                if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                // && (lineString.EndsWith("|") || lineString.EndsWith("｜")))
                {
                    if (tlInfo == null)
                    {
                        tlInfo = new TableLinesInfo();
                        tlInfo.StartLineIndex = i;
                    }
                    tlInfo.TableLines.Add(lineString);
                }
                else
                {
                    if (tlInfo != null)
                    {
                        tlInfo.EndLineIndex = (i - 1);
                        tableSourcesList.Add(tlInfo);
                        tlInfo = null;
                    }
                }
            }

            // 如果最后一行就是表格最后一行
            if (tlInfo != null)
            {
                tlInfo.EndLineIndex = (lineStrings.Length - 1);
                tableSourcesList.Add(tlInfo);
                tlInfo = null;
            }

            for (int tableIndex = 0; tableIndex < tableSourcesList.Count; tableIndex++)
            {
                TableLinesInfo tableLinesInfo = tableSourcesList[tableIndex];

                int maxColumnsCount = 0;
                String definition = "";
                int definitionLineIndex = -1;

                bool hasColumnDefinitionLine = false;

                for (int j = 0; j < tableLinesInfo.TableLines.Count; j++)
                {
                    String lineString = tableLinesInfo.TableLines[j];

                    if (IsColumnAlignmentDefinitionLine(lineString))
                    {
                        definition = lineString;
                        definitionLineIndex = j;

                        definition = definition.Replace("｜", "\r\n")
                                .Replace("|", "\r\n").Replace("：", ":")
                                .Replace("－", "-").Replace(" ", "")
                                .Replace("　", "").Replace("\t", "");


                        String[] definitionTexts = definition.Split(new char[2] { '\r', '\n' },
                            StringSplitOptions.RemoveEmptyEntries);

                        foreach (String definitionText in definitionTexts)
                        {
                            if (definitionText.Length == 0)
                                continue;

                            bool left = false;
                            bool right = false;
                            if (definitionText.StartsWith(":") || definitionText.StartsWith("^:"))
                                left = true;

                            if (definitionText.EndsWith(":") || definitionText.EndsWith(":^"))
                                right = true;

                            ColumnAlignment cDefinition;
                            if (left && right)
                            {
                                left = right = false;
                                cDefinition = ColumnAlignment.CENTER;
                            }
                            else if (right)
                            {
                                cDefinition = ColumnAlignment.RIGHT;
                            }
                            else
                            {
                                cDefinition = ColumnAlignment.LEFT;
                            }

                            tableLinesInfo.ColumnAlignments.Add(cDefinition);
                        }

                        hasColumnDefinitionLine = true;
                    }
                    else
                    {
                        var commonLine = lineString.Replace("|", "\t").Replace("｜", "\t");
                        if (commonLine.EndsWith("\t"))
                        {
                            commonLine = commonLine.Substring(0, commonLine.Length - 1);
                        }

                        if (commonLine.StartsWith("\t"))
                        {
                            commonLine = commonLine.Substring(1);
                        }

                        String[] spanStrings = commonLine.Split('\t');
                        maxColumnsCount = Math.Max(maxColumnsCount, spanStrings.Length);
                    }

                }// 取出表的各列定义（主要是对齐）

                // 如果没有定义，则自动定义列（取最多的一行定义）。
                if (false == hasColumnDefinitionLine)
                {
                    for (int j = 0; j < tableLinesInfo.TableLines.Count; j++)
                    {
                        String lineString = tableLinesInfo.TableLines[j]
                                .Replace("|", "\t").Replace("｜", "\t");
                        if (lineString.EndsWith("\t"))
                        {
                            lineString = lineString.Substring(0, lineString.Length - 1);
                        }

                        if (lineString.StartsWith("\t"))
                        {
                            lineString = lineString.Substring(1);
                        }

                        String[] spanStrings = lineString.Split('\t');

                        maxColumnsCount = Math.Max(maxColumnsCount, spanStrings.Length);
                    }

                    for (int i = 1; i <= maxColumnsCount; i++)
                    {
                        tableLinesInfo.ColumnAlignments.Add(ColumnAlignment.LEFT);// 默认全左齐。
                    }
                }

                StringBuilder tableBuilder = new StringBuilder();
                tableBuilder.Append("<table>");

                for (int tableLineIndex = 0; (tableLineIndex < definitionLineIndex && tableLineIndex < tableLinesInfo
                        .TableLines.Count); tableLineIndex++)
                {
                    // 表头部分
                    String lineString = tableLinesInfo.TableLines[tableLineIndex];

                    Regex regex = new Regex(@"[ 　\t]{1,}");
                    lineString = regex.Replace(lineString, " ");
                    Regex regex2 = new Regex(@"[ 　\t]{0,}[|｜][ 　\t]{0,}");
                    lineString = regex2.Replace(lineString, "|");

                    if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                        lineString = lineString.Substring(1);

                    if (lineString.EndsWith("|") || lineString.EndsWith("｜"))
                        lineString.Substring(0, lineString.Length - 2);

                    lineString = lineString.Replace("｜", "\t").Replace("|", "\t");

                    // 去除头尾。
                    if (lineString.EndsWith("\t"))
                    {
                        lineString = lineString.Substring(0, lineString.Length - 1);
                    }

                    String[] spanStrings = lineString.Split('\t');

                    if (spanStrings.Length > 0)
                    {
                        if (tableLineIndex == 0 && spanStrings.Length == 1)
                        {
                            tableBuilder.Append("<caption>");
                            tableBuilder.Append(spanStrings[0]);
                            tableBuilder.Append("</caption>");
                            continue;
                        }

                        tableBuilder.Append("<tr>");
                        for (int spanIndex = 0;
                            spanIndex < Math.Max(Math.Max(spanStrings.Length, tableLinesInfo.ColumnAlignments.Count), maxColumnsCount);
                            spanIndex++)
                        {

                            if (spanIndex >= spanStrings.Length)
                            {
                                //感觉表头还是全部居中比较好看。
                                tableBuilder.Append("<th style=\"TEXT-ALIGN: center;\">");
                                tableBuilder.Append("</th>");
                                // 添加空单元格。

                                continue;// 防止用户一行末尾未填空内容。
                            }

                            string s = ConvertChar(spanStrings[spanIndex]);

                            //感觉表头还是全部居中对齐比较好看。
                            tableBuilder.Append("<th style=\"TEXT-ALIGN: center;\">");
                            tableBuilder.Append(s == "" ? "　" : s);
                            tableBuilder.Append("</th>");
                        }
                        tableBuilder.Append("</tr>");
                    }
                }

                for (int index = definitionLineIndex + 1; index < tableLinesInfo
                        .TableLines.Count; index++)
                {
                    // 表体部分

                    String lineString = tableLinesInfo.TableLines[index];

                    Regex regex1 = new Regex(@"[ 　\t]{1,}");
                    lineString = regex1.Replace(lineString, " ");
                    Regex regex2 = new Regex(@"[ 　\t]{0,}[|｜][ 　\t]{0,}");
                    lineString = regex2.Replace(lineString, "|");

                    if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                        lineString = lineString.Substring(1);

                    if (lineString.EndsWith("|") || lineString.EndsWith("｜"))
                        lineString.Substring(0, lineString.Length - 2);

                    lineString = lineString.Replace("｜", "\t").Replace("|", "\t");

                    // 去除头尾。
                    if (lineString.EndsWith("\t"))
                    {
                        lineString = lineString.Substring(0, lineString.Length - 1);
                    }

                    String[] spanStrings = lineString.Split('\t');

                    if (spanStrings.Length > 0)
                    {
                        tableBuilder.Append("<tr>");
                        for (int spanIndex = 0;
                            spanIndex < Math.Max(Math.Max(spanStrings.Length, tableLinesInfo.ColumnAlignments.Count), maxColumnsCount);
                            spanIndex++)
                        {
                            if (spanIndex >= tableLinesInfo.ColumnAlignments.Count)
                            {
                                tableBuilder.Append("<td>");
                            }

                            if (spanIndex >= spanStrings.Length)
                            {
                                // 要取出对齐
                                ColumnAlignment align = GetColumnAlign(tableLinesInfo.ColumnAlignments, spanIndex);

                                switch (align)
                                {
                                    case ColumnAlignment.CENTER:
                                        {
                                            tableBuilder
                                                    .Append("<td style=\"TEXT-ALIGN: center;\">");
                                            break;
                                        }
                                    case ColumnAlignment.RIGHT:
                                        {
                                            tableBuilder
                                                    .Append("<td style=\"TEXT-ALIGN: right;\">");
                                            break;
                                        }
                                    default:
                                        tableBuilder
                                                .Append("<td style=\"TEXT-ALIGN: left;\">");
                                        break;
                                }
                                tableBuilder.Append("　</td>");
                                // 添加空单元格。

                                continue;// 防止用户一行末尾未填空内容。
                            }

                            string s = ConvertChar(spanStrings[spanIndex]);
                            // 要取出对齐
                            ColumnAlignment align2 = GetColumnAlign(tableLinesInfo.ColumnAlignments, spanIndex);

                            switch (align2)
                            {
                                case ColumnAlignment.CENTER:
                                    {
                                        tableBuilder
                                                .Append("<td style=\"TEXT-ALIGN: center;\">");
                                        break;
                                    }
                                case ColumnAlignment.RIGHT:
                                    {
                                        tableBuilder
                                                .Append("<td style=\"TEXT-ALIGN: right;\">");
                                        break;
                                    }
                                default:
                                    tableBuilder
                                            .Append("<td style=\"TEXT-ALIGN: left;\">");
                                    break;
                            }
                            tableBuilder.Append(s == "" ? "　" : s);
                            tableBuilder.Append("</td>");
                        }
                        tableBuilder.Append("</tr>");
                    }
                }

                tableBuilder.Append("</table>\n");

                tableLinesInfo.TableHtmlText = tableBuilder.ToString();
            }
            #endregion 格式化表格

            int regionHeaderNumber = 0;
            int regionTailNumber = 0;
            Stack<int> panelLayerStack = new Stack<int>();//此变量只用于记录嵌套的层数，不是直接的序列

            #region 处理六级标题，自动添加数字序号
            var levelIndex1 = startH1Number - 1;
            var levelIndex2 = 0;
            var levelIndex3 = 0;
            var levelIndex4 = 0;
            var levelIndex5 = 0;
            var levelIndex6 = 0;
            #endregion

            // 拼接。
            StringBuilder sbBuilder = new StringBuilder();
            for (int index = 0; index < lineStrings.Length; index++)
            {
                if (string.IsNullOrWhiteSpace(lineStrings[index])) continue;

                //处理删除线标记（代码块中不转变）。
                if (lineStrings[index].StartsWith("    ") == false && lineStrings[index].StartsWith("\t") == false)
                {
                    lineStrings[index] = lineStrings[index].Replace("[=", "<s>").Replace("[＝", "<s>").Replace("【＝", "<s>").Replace("【=", "<s>")
                        .Replace("=]", "</s>").Replace("＝】", "</s>").Replace("=】", "</s>").Replace("＝]", "</s>")
                        .Replace("[\\=", "[=").Replace("[\\＝", "[=").Replace("【\\＝", "[=").Replace("【\\=", "[=")
                        .Replace("=\\]", "=]").Replace("＝\\】", "=]").Replace("=\\】", "=]").Replace("＝\\]", "=]");
                }

                #region 处理六级标题，自动添加数字序号
                if (Globals.MainWindow.AutoNumberHeaders && forbiddenAutoNumber == false)
                {
                    string titleHeader;
                    string titleTail;
                    var titleLevel = HeaderLevel(lineStrings[index], out titleHeader, out titleTail);
                    switch (titleLevel)
                    {
                        case 1:
                            {
                                levelIndex1++;
                                levelIndex2 = levelIndex3 = levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                        case 2:
                            {
                                levelIndex2++;
                                levelIndex3 = levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                        case 3:
                            {
                                levelIndex3++;
                                levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                        case 4:
                            {
                                levelIndex4++;
                                levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                        case 5:
                            {
                                levelIndex5++;
                                levelIndex6 = 0;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                        case 6:
                            {
                                levelIndex6++;
                                lineStrings[index] = titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6) + titleTail;
                                break;
                            }
                    }
                }
                #endregion

                #region 处理自定义折叠区域

                Regex regexStart = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}?[ＩｉＷｗＥｅＱｑIiWwEeQq]?[ 　\t]*\{");
                var matchStart = regexStart.Match(lineStrings[index]);
                if (matchStart != null && matchStart.Success)
                {
                    regionHeaderNumber++;//要先加才能保持尾部索引一致
                    panelLayerStack.Push(regionHeaderNumber);
                    var regionHeaderText = lineStrings[index].Substring(matchStart.Length);
                    var hideBottomBorderStyleText = "";
                    if (string.IsNullOrWhiteSpace(regionHeaderText.Replace("　", " ")))
                    {
                        hideBottomBorderStyleText = "border-bottom-color:transparent;border-bottom-width:0;";
                        regionHeaderText = "";//不再采用两个全角空格强制显示的办法，这样更节省空间。
                    }

                    var regionClassText = "region";
                    Regex regexStartI = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[ＩｉIi][ 　\t]{0,}\{");
                    if (regexStartI.Match(lineStrings[index]).Success)
                    {
                        hideBottomBorderStyleText += "padding-left: 1em;";
                        hideBottomBorderStyleText += "font-weight : bold;";
                        hideBottomBorderStyleText += "text-indent: 0px;";
                        hideBottomBorderStyleText += "text-align: left;";
                        regionClassText = "region_i";
                    }

                    Regex regexStartW = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[ＷｗWw][ 　\t]{0,}\{");
                    if (regexStartW.Match(lineStrings[index]).Success)
                    {
                        hideBottomBorderStyleText += "padding-left: 1em;";
                        hideBottomBorderStyleText += "font-weight : bold;";
                        hideBottomBorderStyleText += "text-indent: 0px;";
                        hideBottomBorderStyleText += "text-align: left;";
                        regionClassText = "region_w";
                    }

                    Regex regexStartE = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[ＥｅEe][ 　\t]{0,}\{");
                    if (regexStartE.Match(lineStrings[index]).Success)
                    {
                        hideBottomBorderStyleText += "padding-left: 1em;";
                        hideBottomBorderStyleText += "font-weight : bold;";
                        hideBottomBorderStyleText += "text-indent: 0px;";
                        hideBottomBorderStyleText += "text-align: left;";
                        regionClassText = "region_e";
                    }

                    Regex regexStartQ = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[ＱｑQq][ 　\t]{0,}\{");
                    if (regexStartQ.Match(lineStrings[index]).Success)
                    {
                        hideBottomBorderStyleText += "padding-left: 1em;";
                        hideBottomBorderStyleText += "font-weight : bold;";
                        hideBottomBorderStyleText += "text-align: left;";
                        hideBottomBorderStyleText += "text-indent: 0px;";
                        regionClassText = "region_q";
                    }

                    hideBottomBorderStyleText = $"style='{hideBottomBorderStyleText}'";

                    lineStrings[index] = $"<div class='{regionClassText}'><p class='region_header' id='region_header_{regionHeaderNumber}'{hideBottomBorderStyleText}>{regionHeaderText}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'><table><tr><td>";

                    lineStrings[index] += "<script>$(document).ready(function() {" +
                                             $@"$('#region\_header\_{regionHeaderNumber}').click(function() {{" +
                                                $@"$('#region\_panel\_{regionHeaderNumber}').toggle();" +
                                            "});" +
                                        "}); </script><p>";
                }
                else
                {
                    regionTailNumber++;
                    Regex regexEnd = new Regex(@"^[ 　]{0,3}\}[ ]{0,}?[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ ]{0,}?");
                    var matchEnd = regexEnd.Match(lineStrings[index]);
                    if (matchEnd != null && matchEnd.Success)
                    {
                        var regionTailText = lineStrings[index].Substring(matchEnd.Length);
                        var hideTopBorderStyleText = "";
                        if (string.IsNullOrWhiteSpace(regionTailText.Replace("　", " ")))
                        {
                            hideTopBorderStyleText = " style='border-top-color:transparent;border-bottom-width:0;'";
                            regionTailText = "";//不再采用两个全角空格强制显示的办法，这样更节省空间。
                        }
                        int panelNumber = 0;
                        if (panelLayerStack.Count > 0)
                        {
                            panelNumber = panelLayerStack.Pop();
                        }

                        lineStrings[index] = $"</p></td></tr></table>\n</div><p class='region_tail' id='region_tail_{panelNumber}'{hideTopBorderStyleText}>{regionTailText}</p></div>";

                        lineStrings[index] += "<script>$(document).ready(function() {" +
                                         $@"$('#region\_tail\_{panelNumber}').click(function() {{" +
                                            $@"$('#region\_panel\_{panelNumber}').toggle();" +
                                        "});" +
                                    "}); </script>";
                    }
                    else
                    {
                        Regex regexSplitter = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}.{0,}[.。]{3,}[ 　\t]{0,}$");
                        //这行中必须有：...region...，此行为分栏符。
                        var matchSplitter = regexSplitter.Match(lineStrings[index]);
                        if (matchSplitter != null && matchSplitter.Success)
                        {
                            lineStrings[index] = "</td><td>";
                        }
                    }
                }
                #endregion

                #region 处理冒号开头的注释文本
                if (lineStrings[index].StartsWith(":") || lineStrings[index].StartsWith("："))
                {
                    //以冒号开头的，作为备注。
                    sbBuilder.Append($"<p class='comment'>{lineStrings[index].Substring(1)}</p>\r\n");
                    sbBuilder.Append("\r\n");
                    continue;
                }
                #endregion

                #region 处理材料出处文本

                Regex regexMaterialSource = new Regex(@"^[ 　]{0,3}([>》〉]{1,1}[>》〉 　\t]{0,}){0,}——[ 　\t]{0,}《.*》.{0,}$");
                Match matchMaterialSource = regexMaterialSource.Match(lineStrings[index]);
                if (matchMaterialSource != null && matchMaterialSource.Success)
                {
                    Regex regexBlockQuoter = new Regex(@"^[ 　]{0,3}[>》〉]{1,1}[>》〉 　\t]{0,}");
                    var blockQuoterHeader = "";
                    var matchBlockQuoter = regexBlockQuoter.Match(lineStrings[index]);
                    string lineTailTextOfBlockQuoter;
                    if (matchBlockQuoter != null && matchBlockQuoter.Success)
                    {
                        blockQuoterHeader = lineStrings[index].Substring(0, matchBlockQuoter.Length)
                            .Replace("》", ">").Replace("〉", ">")
                            .Replace("　", "").Replace(" ", "").Replace("\t", "").Replace(">", "> ");
                        lineTailTextOfBlockQuoter = lineStrings[index].Substring(matchBlockQuoter.Length).Trim(new char[] { ' ', '　', '\t' });
                    }
                    else
                    {
                        lineTailTextOfBlockQuoter = lineStrings[index].Trim(new char[] { ' ', '　', '\t' });
                    }

                    Regex regexMaterialSourceStartMark = new Regex(@"——[ 　\t]{0,}《");
                    Match matchMaterialSourceStartMark = regexMaterialSource.Match(lineTailTextOfBlockQuoter);
                    if (matchMaterialSourceStartMark != null && matchMaterialSourceStartMark.Success)
                    {
                        lineTailTextOfBlockQuoter = regexMaterialSourceStartMark.Replace(lineTailTextOfBlockQuoter, "——《");
                    }

                    sbBuilder.Append($"{blockQuoterHeader}<p class='ex_m_cc'>{lineTailTextOfBlockQuoter}</p>\r\n");
                    sbBuilder.Append("\r\n");
                    continue;
                }
                #endregion

                #region 处理任务列表项
                if (lineStrings[index].StartsWith("[-]") || lineStrings[index].StartsWith("[－]"))
                {
                    //以[-]开头的，作为尚未开始的任务列表项目。
                    sbBuilder.Append($"<p class=\"task\"><span class='task_u'>[-]</span> <span class='task_up'>{lineStrings[index].Substring(4)}</span></p>");
                    sbBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[%]"))
                {
                    //以[%]开头的，作为正在进行的任务列表项目。
                    sbBuilder.Append($"<p class=\"task\"><span class='task_p'>[%]</span> <span class='task_pp'>{lineStrings[index].Substring(4)}</span></p>");
                    sbBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[+]"))
                {
                    //以[+]开头的，作为已完成任务列表项目。
                    sbBuilder.Append($"<p class=\"task\"><span class='task_f'>[+]</span> <span class='task_fp'><s>{lineStrings[index].Substring(4)}</s></span></p>");
                    sbBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[#]"))
                {
                    //以[#]开头的，作为已废弃任务列表项目。
                    sbBuilder.Append($"<p class=\"task\"><span class='task_a'>[#]</span> <span class='task_ap'><s>{lineStrings[index].Substring(4)}</s></span></p>");
                    sbBuilder.Append("\r\n");
                    continue;
                }

                if (IsDateLine(lineStrings[index]))
                {
                    var className = "date";
                    var indexStatus = -1;
                    indexStatus = lineStrings[index].IndexOf("][-]");
                    if (indexStatus >= 0) className = "date_u";
                    else
                    {
                        indexStatus = lineStrings[index].IndexOf("][%]");
                        if (indexStatus >= 0)
                        {
                            className = "date_p";
                        }
                        else
                        {
                            indexStatus = lineStrings[index].IndexOf("][+]");
                            if (indexStatus >= 0)
                            {
                                className = "date_f";
                            }
                            else
                            {
                                indexStatus = lineStrings[index].IndexOf("][#]");
                                if (indexStatus >= 0)
                                {
                                    className = "date_a";
                                }
                            }
                        }
                    }

                    if (indexStatus >= 0)
                    {
                        sbBuilder.Append("<p><span class='" + className + $"'>{lineStrings[index].Substring(0, indexStatus + 4)}</span>{lineStrings[index].Substring(indexStatus + 5)}</p>");
                        sbBuilder.Append("\r\n");
                        continue;
                    }

                    var indexOfSquareBracket = lineStrings[index].IndexOf("]");
                    if (indexOfSquareBracket < 0)
                    {
                        sbBuilder.Append(lineStrings[index]);
                        sbBuilder.Append("\r\n");
                        continue;
                    }
                    else
                    {
                        sbBuilder.Append($"<p><span class='date'>{lineStrings[index].Substring(0, indexOfSquareBracket + 1)}</span>{lineStrings[index].Substring(indexOfSquareBracket + 1)}</p>");
                        sbBuilder.Append("\r\n");
                        continue;
                    }
                }
                #endregion

                #region 处理表格信息
                TableLinesInfo tableLinesInfo = InWhitchTable(index, tableSourcesList);

                if (null == tableLinesInfo)
                {
                    sbBuilder.Append(lineStrings[index] + separatorText[0]);
                    sbBuilder.Append("\r\n");
                    continue;
                }

                if (tableLinesInfo.IsUsed) continue;

                sbBuilder.Append(tableLinesInfo.TableHtmlText);
                tableLinesInfo.IsUsed = true;
                #endregion
            }

            return sbBuilder.ToString();
        }

        /// <summary>
        /// 取 Header 标题的层级索引值。
        /// </summary>
        /// <param name="lI1">一级标题会使前面所有高于一级的标题索引重置，并使一级标题的索引值加1。</param>
        /// <param name="lI2">二级标题会使前面所有高于二级的标题索引重置，并使二级标题的索引值加1。</param>
        /// <param name="lI3">三级标题会使前面所有高于三级的标题索引重置，并使三级标题的索引值加1。</param>
        /// <param name="lI4">四级标题会使前面所有高于四级的标题索引重置，并使四级标题的索引值加1。</param>
        /// <param name="lI5">五级标题会使前面所有六级标题索引重置，并使五级标题的索引值加1。</param>
        /// <param name="lI6">六级标题只会使六级标题索引加1。</param>
        /// <returns></returns>
        private static string BuildHeaderIndexText(int lI1, int lI2, int lI3, int lI4, int lI5, int lI6)
        {
            if (lI1 <= 0) return "";

            StringBuilder sb = new StringBuilder();
            sb.Append(lI1);

            if (lI2 <= 0) return sb.ToString() + ".";
            sb.Append("." + lI2);
            if (lI3 <= 0) return sb.ToString() + ".";
            sb.Append("." + lI3);
            if (lI4 <= 0) return sb.ToString() + ".";
            sb.Append("." + lI4);
            if (lI5 <= 0) return sb.ToString() + ".";
            sb.Append("." + lI5);
            if (lI6 <= 0) return sb.ToString() + ".";
            sb.Append("." + lI6);

            return sb.ToString() + ".";
        }

        /// <summary>
        /// 取 Header 标题的层级。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="headerMark">传出 Header 标题的标志文本。</param>
        /// <param name="headerContent">传出 Header 标题的内容文本。</param>
        private static int HeaderLevel(string lineText, out string headerMark, out string headerContent)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                headerMark = "";
                headerContent = lineText;
                return 0;
            }

            Regex regex = new Regex("^ {0,3}[＃#]{1,6}");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                var count = 0;
                foreach (char c in match.Value)
                {
                    if (c == '#' || c == '＃') count++;
                }
                headerMark = match.Value;
                headerContent = lineText.Substring(match.Length);
                return count;
            }

            headerMark = "";
            headerContent = lineText;
            return 0;
        }

        /// <summary>
        /// 是否二维文字表行。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        private static bool IsTableRow(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            if (lineText.Contains("|") == false && lineText.Contains("｜") == false) return false;

            if (lineText.StartsWith("    ") || lineText.StartsWith("　　　　") || lineText.StartsWith("\t")) return false;

            var trim = lineText.Trim();
            if (trim.StartsWith("+ ") || trim.StartsWith("* ") || trim.StartsWith("- ")) return false;

            if (lineText.StartsWith(">") || lineText.StartsWith("》")) return false;

            return true;
        }

        /// <summary>
        /// 处理转义字符：
        /// \*
        /// \\
        /// \`
        /// \_
        /// \{
        /// \}
        /// \[
        /// \]
        /// \(
        /// \)
        /// \#
        /// \+
        /// \-
        /// \.
        /// \!
        /// </summary>
        /// <param name="sourceText">源文本。</param>
        public static string ConvertChar(string sourceText)
        {
            sourceText = sourceText.Replace("\\*", "*").Replace("\\\\", "\\").Replace("\\`", "`")
                .Replace("\\_", "_").Replace("\\{", "{").Replace("\\}", "}").Replace("\\[", "[")
                .Replace("\\]", "]").Replace("\\(", "(").Replace("\\)", ")").Replace("\\#", "#")
                .Replace("\\+", "+").Replace("\\-", "-").Replace("\\.", ".").Replace("\\!", "!");
            return sourceText;
        }

        /// <summary>
        /// 取二维文字表列对齐方式。
        /// </summary>
        /// <param name="columnDefinitions">列定义对象集合。</param>
        /// <param name="index">索引值，指定哪一列。</param>
        private static ColumnAlignment GetColumnAlign(List<ColumnAlignment> columnDefinitions, int index)
        {
            if (index < 0 || index >= columnDefinitions.Count
                    || columnDefinitions.Count == 0)
                return ColumnAlignment.LEFT;

            return columnDefinitions[index];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="tableLinesInfos"></param>
        /// <returns></returns>
        private static TableLinesInfo InWhitchTable(int index, List<TableLinesInfo> tableLinesInfos)
        {
            foreach (TableLinesInfo t in tableLinesInfos)
            {
                if (index >= t.StartLineIndex && index <= t.EndLineIndex)
                    return t;
            }

            return null;
        }

        public static bool IsColumnAlignmentDefinitionLine(string text)
        {
            if (string.IsNullOrEmpty(text)) return false;
            if (text.Contains("-") == false && text.Contains("－") == false) return false;//防止将空表行当作表的“列对齐定义行”。

            foreach (var c in text)
            {
                if (c == '-' || c == '－' || c == '|' || c == '｜' || c == ' '
                    || c == '　' || c == '\t' || c == ':' || c == '：' || c == '^')
                    continue;//^表示该列应自动顺序编号
                return false;
            }

            return true;
        }

        public static bool IsNumber(char @char)
        {
            if ((@char >= '1' && @char <= '9') || @char == '0') return true;
            if ((@char >= '１' && @char <= '９') || @char == '０') return true;

            return false;
        }

        public static bool IsHorizontalLineText(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;
            if (lineText.StartsWith("\t")) return false;//以tab开头的是代码块<code>

            foreach (char c in lineText)
            {
                if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '\t')//中间带tab符也算，但不能以tab符开头。
                    return false;
            }

            return true;
        }

        /// <summary>
        /// 判断此行文本是否只包含“｜”、“=”等。这样的行是“表格格式化功能”自动添加的分割表标题与表头行的装饰行。
        /// </summary>
        /// <param name="lineText">行有意思一。</param>
        public static bool IsTableCaptionSplittor(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            foreach (char c in lineText)
            {
                if (c == '=' || c == '｜' || c == '|' || c == '＝') continue;

                return false;
            }

            return true;
        }

        /// <summary>
        /// 任务列表的状态。
        /// </summary>
        public enum TaskListItemState
        {
            NotTaskListItem, UnStart, Precessing, Finished, Aborted
        }

        /// <summary>
        /// 取格式化后的 Markdown 文本。
        /// </summary>
        /// <param name="srcMarkdownText">尚未格式化的 Markdown 文本。</param>
        /// <returns></returns>
        public static string FormatMarkdownText(string srcMarkdownText)
        {
            string splitter = Environment.NewLine;

            var lines = srcMarkdownText.Split(new string[] { splitter }, StringSplitOptions.None);//注意：空行有用，引用和正常文本间的切换需要空行。

            var sb = new StringBuilder();
            var previewHeaderLevel = 0;
            var orderListNumber = 0;
            for (int i = 0; i < lines.Length; i++)
            {
                var text = lines[i].Replace("\r", "").Replace("\n", "");

                #region 保留空行
                var trimedText = text.TrimStart(new char[] { ' ', '　', '\t' });

                if (string.IsNullOrEmpty(trimedText))//保留空行。
                {
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 代码块原样输出
                if (text.StartsWith("    ") || text.StartsWith("\t"))
                {
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化被嵌入到引用块中的标题
                Match match2;
                if (IsHeaderInBlockQuote(text, out match2))
                {
                    if (match2 != null && match2.Success)
                    {
                        StringBuilder sbTitleAfterBlockQuoter = new StringBuilder();
                        foreach (char c in match2.Value)
                        {
                            if (c == '#' || c == '＃')
                            {
                                sbTitleAfterBlockQuoter.Append('#');
                            }
                        }
                        text = sbTitleAfterBlockQuoter.ToString() + text.Substring(match2.Length);
                    }
                }
                #endregion

                #region 格式化标题
                if (IsHeaderLine(text))
                {
                    Regex reg = new Regex(@"^[ 　]{0,3}[#＃]{1,6}");
                    var match = reg.Match(text);
                    if (match != null && match.Success)
                    {
                        //防止出现后一个标题比前一个层级高2层以上的情况
                        //例如，前一个标题是一级，紧跟着的一个标题却是三级，这不合逻辑。
                        string titleHeader;
                        string titleTail;
                        var titleLevel = HeaderLevel(text, out titleHeader, out titleTail);
                        if (titleLevel > previewHeaderLevel + 1)
                        {
                            titleLevel = previewHeaderLevel + 1;
                            previewHeaderLevel = titleLevel;

                            StringBuilder sbHeader = new StringBuilder();
                            for (int x = 0; x < titleLevel; x++)
                            {
                                sbHeader.Append("#");
                            }
                            titleHeader = sbHeader.ToString();
                            sb.Append(titleHeader);
                            sb.Append(titleTail);
                            sb.Append(splitter);
                            continue;
                        }
                        else
                        {
                            previewHeaderLevel = titleLevel;

                            sb.Append(match.Value.Replace("＃", "#"));
                            sb.Append(text.Substring(match.Length));
                            sb.Append(splitter);
                            continue;
                        }
                    }
                }
                #endregion

                #region 日期行
                if (IsDateLine(text))
                {
                    sb.Append(FormatDateLine(text));
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region TODO 型特殊注释文本行
                string newTodoCommentMark;
                string newTodoCommentTail;
                if (IsTodoCommentLine(text, out newTodoCommentMark, out newTodoCommentTail))
                {
                    sb.Append("；TODO：" + newTodoCommentTail);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region [Menu] 型特殊注释文本行
                string newMenuMarkTail;
                if (IsMenuMarkLine(text, out newMenuMarkTail))
                {
                    sb.Append("；[Menu]：" + newMenuMarkTail);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 演示模式 型特殊注释文本行

                string newPresentationTypeTail;
                if (IsPresentationTypeLine(text, out newPresentationTypeTail))
                {
                    sb.Append("；PM：" + newPresentationTypeTail);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 与试题相关的行
                if (MarkDownEditorBase.IsStartWithTestPaperKeyWord(text))
                {
                    //text = trimedText;//试题元素前的全角空格需要保留。
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 备注，以分号或冒号开头
                if (IsCommentLine(text))
                {
                    //不再要求顶格，保留空格。全格式化成半角空格。
                    var commentStartIndex = 1;
                    var isCompileComment = false;
                    for (int iComment = 0; iComment < text.Length; iComment++)
                    {
                        var c = text[iComment];
                        if (c == ' ') continue;
                        else if (c == '　') continue;
                        else if (c == '\t') continue;
                        else if (c == '；' || c == ';')
                        {
                            commentStartIndex = iComment + 1;
                            break;
                        }
                        else if (c == '：' || c == ':')
                        {
                            commentStartIndex = iComment + 1;
                            isCompileComment = true;
                            break;
                        }
                    }

                    if (isCompileComment)
                    {
                        text = "：" + text.Substring(commentStartIndex);
                    }
                    else
                    {
                        text = "；" + text.Substring(commentStartIndex);
                    }
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 如果是在树型文字表中的备注行（：开头注释行）

                Regex regCommentInTreeTextTable = new Regex(@"^([!！][　├│└]{0,}){1,}[：:]+");
                var matchCommentInTreeTextTable = regCommentInTreeTextTable.Match(text);
                if (matchCommentInTreeTextTable.Success)
                {
                    var header = matchCommentInTreeTextTable.Value;
                    if (header.EndsWith(":"))
                    {
                        header = header.Substring(0, header.Length - 1) + "：";
                    }

                    text = header + text.Substring(matchCommentInTreeTextTable.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 任务列表，必须顶格，否则易与Code块冲突
                //形式是：[-][%][+][#]，减号表示未开始，%号表示正在进行，加号表示已完成，[#]表示废弃。
                //两个方括弧间可以有空格。
                if (IsTaskLine(text))
                {
                    TaskListItemState state = TaskListItemState.UnStart;
                    int index = text.IndexOf(']');
                    char[] trimArray = new char[] { ' ', '\t', '　' };
                    var t1 = text.Substring(0, index + 1).Trim(trimArray);
                    var t2 = text.Substring(index + 1);//跳过 ]

                    foreach (char c in t1)
                    {
                        if (c == '[' || c == ']' || c == ' ' || c == '\t' || c == '　') continue;//允许空格。
                        if (c == '-' || c == '－')
                        {
                            state = TaskListItemState.UnStart;
                            break;
                        }
                        else if (c == '%')
                        {
                            state = TaskListItemState.Precessing;
                            break;
                        }
                        else if (c == '+')
                        {
                            state = TaskListItemState.Finished;
                            break;
                        }
                        else
                        {
                            state = TaskListItemState.NotTaskListItem;
                            break;
                        }
                    }

                    switch (state)
                    {
                        case TaskListItemState.UnStart:
                            {
                                text = "[-] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Finished:
                            {
                                text = "[+] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Precessing:
                            {
                                text = "[%] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Aborted:
                            {
                                text = "[#] " + t2.TrimStart(trimArray);
                                break;
                            }
                        default:
                            {
                                break;//非任务列表项，直接返回原文本。
                            }
                    }

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 文档总标题，总是顶格
                var htmlDocumentTitle = GetDocumentTitle(text);
                if (htmlDocumentTitle != null)
                {
                    text = text.Trim(' ', '\t', '　');
                    if (text.ToLower().StartsWith("title>"))
                    {
                        text = "%" + text.Substring(6);
                    }
                    else if (text.StartsWith("标题>"))
                    {
                        text = "%" + text.Substring(3);
                    }

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 作者信息，总是顶格
                if (text.StartsWith("@") || text.StartsWith("·"))
                {
                    text = text.TrimStart(' ', '\t', '　');
                    text = "@" + text.Substring(1).Trim();

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 如果是水平线，总是顶格
                if (IsHorizontalLineText(text))
                {
                    text = text.Replace("　", "").Replace("\t", "").Replace("－", "-");

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化块引用
                Regex regexBlockQuoter = new Regex(@"^[ 　]{0,3}([>》〉]{1}[ 　]{0,}){1,}");
                var matchBlockQuoter = regexBlockQuoter.Match(text);
                if (matchBlockQuoter != null && matchBlockQuoter.Success)
                {
                    text = matchBlockQuoter.Value.Replace(" ", "").Replace("　", "").Replace("》", ">").Replace("〉", ">").Replace(">", "> ") +
                        text.Substring(matchBlockQuoter.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 无序列表
                if (text.StartsWith("－") || text.StartsWith("-") || text.StartsWith("+"))
                {
                    //无序列表
                    int index = -1;
                    for (int j = 0; j < text.Length; j++)
                    {
                        char c = text[j];
                        if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '+' && c != '*')
                        {
                            index = j;
                            break;
                        }
                    }

                    if (index >= 0)
                    {
                        if (index >= text.Length) text = "";//不允许空无序列表存在
                        else
                        {
                            text = text.Substring(0, index).Replace("-", "+").Replace("－", "+").Replace("　", "").Replace(" ", "").Replace("*", "+").Replace("+", "+ ") + text.Substring(index);
                        }
                    }
                    else
                    {
                        if (IsHorizontalLineText(text) == false)//以-开头还可能是水平线，不能去除
                        {
                            text = "";
                        }
                        else
                        {
                            text = text.Replace("\t", "").Replace("－", "-").Replace("　", "").Replace(" ", "");
                        }
                    }
                }
                else if (text.StartsWith("* ") || text.StartsWith("*　"))
                {
                    //*号开头的比较复杂，因为会和加粗、倾斜冲突——所以必须在星号后跟一个空格才算。

                    //仍然是无序列表
                    int index = -1;
                    for (int j = 0; j < text.Length; j++)
                    {
                        char c = text[j];
                        if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '+' && c != '*')
                        {
                            index = j;
                            break;
                        }
                    }

                    if (index >= 0)
                    {
                        if (index >= text.Length) text = "";//不允许空无序列表存在
                        else
                        {
                            text = text.Substring(0, index).Replace("-", "+").Replace("－", "+").Replace("　", "").Replace(" ", "").Replace("*", "+").Replace("+", "+ ") + text.Substring(index);
                        }
                    }
                    else
                    {
                        text = "";
                    }

                }//else... 否则只是加粗或倾斜。一行文本开头就加粗或倾斜是完全有可能的。

                #endregion

                #region 有序列表

                Regex regexOrderedList = new Regex(@"^[ 　]{0,3}[0123456789０１２３４５６７８９]{1,}\.[ 　\t]*");
                var matchOrderedList = regexOrderedList.Match(text);
                if (matchOrderedList.Success)
                {
                    text = $"{++orderListNumber}. " + text.Substring(matchOrderedList.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {
                    if (string.IsNullOrWhiteSpace(text) == false) orderListNumber = 0;
                }

                #endregion

                #region 添加了单独的“表格格式化”功能，此处不需要再格式化，否则反而会冲突。
                //text = text.Replace("|", "｜");

                //if (text.Contains("|") || text.Contains("｜"))
                //{
                //    var lineContent = text.Trim(new char[] { ' ', '　', '\t' });
                //    if (lineContent.StartsWith("|") == false && lineContent.StartsWith("｜") == false)
                //    {
                //        text = "｜" + lineContent;
                //    }

                //    if (lineContent.EndsWith("|") == false && lineContent.EndsWith("｜") == false)
                //    {
                //        text += "｜";
                //    }
                //}

                //if (IsColumnDefinitionLine(text))
                //{
                //    text = text.Replace("：", ":").Replace("-", "－");
                //}
                #endregion

                #region 格式化自定义折叠区域
                Regex regexStart = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}?[ＩｉＷｗＥｅＱｑIiWwEeQq]?[ 　\t]*\{");
                if (regexStart.Match(text).Success)
                {
                    text = FormatRegionStartHeader(text);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {

                    Regex regexEnd = new Regex(@"^[ 　]{0,3}\}[ 　\t]*[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}");
                    var matchEnd = regexEnd.Match(text);
                    if (matchEnd != null && matchEnd.Success)
                    {
                        text = "} region " + text.Substring(matchEnd.Length);
                        sb.Append(text);
                        sb.Append(splitter);
                        continue;
                    }
                }

                #region 格式化 region 分割线
                if (IsRegionSplitter(text))
                {
                    Regex regRegionSplitterStart = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ]");
                    var matchRegionSplitterStart = regRegionSplitterStart.Match(text);
                    if (matchRegionSplitterStart != null && matchRegionSplitterStart.Success)
                    {
                        sb.Append("... region ");
                        var rSplitterContent = text.Substring(matchRegionSplitterStart.Length);
                        Regex regRegionSplitterEnd = new Regex(@"[.。]{3,}[ 　\t]{0,}$");
                        var matchRegionSplitterEnd = regRegionSplitterEnd.Match(rSplitterContent);
                        if (matchRegionSplitterEnd != null && matchRegionSplitterEnd.Success)
                        {
                            rSplitterContent = rSplitterContent.Substring(0, matchRegionSplitterEnd.Index);
                        }
                        sb.Append(rSplitterContent.Trim(new char[] { ' ', '　', '\t' }));
                        sb.Append(" ...");
                        sb.Append(splitter);
                        continue;
                    }
                }
                #endregion

                #endregion

                sb.Append(text);
                sb.Append(splitter);
            }

            return sb.ToString();
        }

        /// <summary>
        /// 格式化自定义折叠区的标头文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        private static string FormatRegionStartHeader(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return "";

            Regex regexStartI = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[ＩｉIi][ 　\t]{0,}\{[ 　\t]{0,}");
            var matchStartI = regexStartI.Match(lineText);
            if (matchStartI != null && matchStartI.Success)
            {
                return "region i { " + lineText.Substring(matchStartI.Length);
            }

            Regex regexStartQ = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[qQｑＱ][ 　\t]{0,}\{[ 　\t]{0,}");
            var matchStartQ = regexStartQ.Match(lineText);
            if (matchStartQ != null && matchStartQ.Success)
            {
                return "region q { " + lineText.Substring(matchStartQ.Length);
            }

            Regex regexStartW = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[wWｗＷ][ 　\t]{0,}\{[ 　\t]{0,}");
            var matchStartW = regexStartW.Match(lineText);
            if (matchStartW != null && matchStartW.Success)
            {
                return "region w { " + lineText.Substring(matchStartW.Length);
            }

            Regex regexStartE = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}[eｅEＥ][ 　\t]{0,}\{[ 　\t]{0,}");
            var matchStartE = regexStartE.Match(lineText);
            if (matchStartE != null && matchStartE.Success)
            {
                return "region e { " + lineText.Substring(matchStartE.Length);
            }

            Regex regexStart = new Regex(@"^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}\{[ 　\t]{0,}");
            var matchStart = regexStart.Match(lineText);
            if (matchStart != null && matchStart.Success)
            {
                return "region { " + lineText.Substring(matchStart.Length);
            }

            return lineText;
        }

        public static bool IsImageLinkLine(string text)
        {
            Regex regex = new Regex(@"^!\[.*\]\(.*\).*$");
            var match = regex.Match(text);
            return (match != null && match.Success);
        }

        public static bool IsTreeListTextLine(string text)
        {
            return (text.StartsWith("！") || text.StartsWith("!")) && (IsImageLinkLine(text) == false);
        }

        /// <summary>
        /// 在编译之前，移除一些不必编译到html文件中的行。这些行包括：
        /// ⑴文档总标题行——以%（或%）开头——即全、半角的百分号；
        ///     ★这个比较特殊，只有第一个以%（或%）开头的会被当作标题，其它以%（或%）开头的行会原样参加编译。
        /// ⑵注释行——以;（或；）开头——即全半角分号；
        ///     ☆所有以;（或；）开头的行都会被忽略。
        /// ⑶文档页眉——以~开头。
        /// </summary>
        public static string RemoveExtraLines(string srcText, out string htmlDocumentTitle,
           out string htmlDocumentPageHeader, out string footerText, out bool compilePageMenu)
        {
            htmlDocumentPageHeader = htmlDocumentTitle = footerText = "";
            compilePageMenu = false;

            if (string.IsNullOrEmpty(srcText))
            {
                return string.Empty;
            }

            string[] lines;

            //尝试一下找“类型”，对填空题作些处理。
            //之前在做HistoryAssist的填空题时，一些格式与Markdown不兼容，这里处理一下就能用了。
            var startIndexOfType = srcText.IndexOf("类型＞＞");
            var endIndexOfType = srcText.IndexOf("＜＜类型");
            string typeText = "";
            if (startIndexOfType >= 0 && endIndexOfType >= 0 && endIndexOfType > startIndexOfType)
            {
                typeText = srcText.Substring(startIndexOfType + 4, endIndexOfType - startIndexOfType - 4);
            }

            if (typeText == "填空" || typeText == "填空题")
            {
                srcText = srcText.Replace("【", "`").Replace("】", "`")
                    .Replace("\n//", "\n;").Replace("\r//", "\n;");
            }

            lines = CustomMarkdownSupport.FormatMarkdownText(srcText).Replace("\r\n", "\n")
                .Split(new char[] { '\r', '\n' }, StringSplitOptions.None);//要保留空行


            #region 以惊叹号开头的行被视为树型文字表行，树型文字表以代码方式显示

            //注意：这个格式化不放在 FormatMarkdown()方法中。因为编译时才需要格式化，编辑时不需要格式化。
            for (int x = 0; x < lines.Length; x++)
            {
                if (lines[x].StartsWith("！") || lines[x].StartsWith("!"))
                {
                    if (IsImageLinkLine(lines[x])) continue;

                    lines[x] = "    " + lines[x];
                }
            }

            #endregion


            StringBuilder sb = new StringBuilder();
            bool removedDocumentTitleLine = false;
            bool removeFooterText = false;
            bool removePageHeader = false;

            //char[] trimChars = new char[] { '\t', ' ', '　' };
            //书写时文档标题、备注都必须严格的在行首添加标记字符——否则易与code块冲突。
            //实际编译时执行“三个空格以内皆有效”。

            var startLineIndex = 0;
            if (lines.Length > 0)
            {
                var lineFst = lines[0];
                if (IsTaskLine(lineFst) || IsDateLine(lineFst))
                {
                    //文档完成状态的标记行，不编译进Html。
                    startLineIndex = 1;//忽略第1行
                }
            }

            for (int i = startLineIndex; i < lines.Length; i++)
            {
                string line = lines[i];

                if (removedDocumentTitleLine == false)
                {
                    var title = GetDocumentTitle(line);
                    if (title != null)
                    {
                        htmlDocumentTitle = title;
                        removedDocumentTitleLine = true;//只有第一个以%开头的行才会被当作文档标题。
                        sb.Append("\n");
                        continue;
                    }
                }

                if (IsPageHeader(line))
                {
                    if (removePageHeader == false)
                    {
                        htmlDocumentPageHeader = line.Substring(1).Trim();
                        removePageHeader = true;//只有第一个以~开头的行才会被当作页眉。
                        sb.Append("\n");
                        continue;
                    }
                }
                else if (IsPageFoot(line))//即PageFooter
                {
                    if (removeFooterText == false)
                    {
                        footerText = line.Substring(1);
                        removeFooterText = true;
                        sb.Append("\n");
                        continue;
                    }
                }
                else if (IsCommentLine(line) && IsCompileCommentLine(line) == false)
                {
                    if (IsMenuMarkText(line))
                    {
                        compilePageMenu = true;
                    }
                    sb.Append("\n");
                    continue;
                    //以;（或；）号开头的行是注释行，不会被编译进html文档。
                }
                //任务列表原样输出！不再作为备注！2016年2月5日
                //else if (IsTaskLine(line))
                //{
                //    sb.Append("\n");
                //    continue;
                //    //以“[-]、[%]、[+]”开头的行也是注释，但是比较特殊，属于“任务列表”。
                //}
                else if (IsTableCaptionSplittor(line))
                {
                    //sb.Append("\n");//这个必须去除，否则表格的标题会与表格体分离——标题会成为一个单独的表格（只有一个单元格）
                    continue;
                }
                //下面这些也应去除。
                //＜＜＜信息＞
                // 标题＞＞明清君主专制制度的加强＜＜标题
                // 日期＞＞2013年4月14日＜＜日期
                // 作者＞＞杨震宇＜＜作者
                // 电邮＞＞historyassist@163.com＜＜电邮
                // 备注＞＞必修Ｉ，第一专题，第四节＜＜备注
                //＜信息＞＞＞
                else if (line.StartsWith("＜＜＜信息＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　类型＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　标题＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　日期＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　作者＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　辑录＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　电邮＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　备注＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　电邮＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("＜信息＞＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else
                {
                    //处理锚
                    //改造一下Markdown语法，
                    //按Markdown原始语法，[链接名](http://www.xxx.com)表示一个链接。
                    //但我这里的“锚”，会是这个样子[锚名](@锚ID)
                    //锚名可以为空，锚ID必须以#号开头——否则它就只是一个普通的链接。
                    var text = line;

                    if (text.StartsWith("    ") || text.StartsWith("\t"))
                    {
                        sb.Append(line);
                        sb.Append("\n");
                        continue;
                    }

                    if (text.Contains("[") == false || text.Contains("]") == false)
                    {
                        sb.Append(line);
                        sb.Append("\n");
                        continue;
                    }

                    if (text.Contains("(") == false && text.Contains("（") == false)
                    {
                        sb.Append(line);
                        sb.Append("\n");
                        continue;
                    }

                    if (text.Contains(")") == false && text.Contains("）") == false)
                    {
                        sb.Append(line);
                        sb.Append("\n");
                        continue;
                    }

                    text = text.Replace("[", "[[^]][")
                        .Replace("（", "(").Replace("）", ")")
                        .Replace(")", ")[[^]]").Replace("：", ":");

                    var spans = text.Split(new string[] { "[[^]]" }, StringSplitOptions.RemoveEmptyEntries);
                    var sb2 = new StringBuilder();
                    foreach (var s in spans)
                    {
                        var index = s.IndexOf("](@");
                        if (s.StartsWith("[") && s.EndsWith(")") && index > 0)
                        {
                            //此片段是一个锚
                            //例如：[abc](@abcde)
                            //abc是锚名，可以省略；
                            //abcde是锚的ID,不可省略。

                            var anchorName = s.Substring(1, index - 1);
                            var anchorID = s.Substring(index + 3, s.Length - index - 4)
                                .Replace("　", "_").Replace("\t", "_").Replace(" ", "_");

                            if (string.IsNullOrEmpty(anchorID))
                            {
                                sb2.Append(s);
                            }
                            else
                            {
                                //当锚ID不为空时，才算是个锚，才需要编译
                                //锚的名称可以为空。
                                //如果锚名为空，浏览编译后的html文档时，
                                //浏览器不会呈现这个锚——但是对这个锚的链接仍然有效。
                                sb2.Append("<span id=\"" + anchorID + "\" class=\"anchor\">" + anchorName + "</span>");
                            }
                        }
                        else sb2.Append(s);
                    }

                    line = sb2.ToString();
                }

                sb.Append(line);
                sb.Append("\n");
            }

            //尝试再找下标题
            if (string.IsNullOrEmpty(htmlDocumentTitle))
            {
                var startIndex = srcText.IndexOf("标题＞＞");
                var endIndex = srcText.IndexOf("＜＜标题");

                if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                {
                    htmlDocumentTitle = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                }
            }

            if (string.IsNullOrEmpty(footerText))
            {
                //脚注一般用于显示作者
                var startIndex = srcText.IndexOf("作者＞＞");
                var endIndex = srcText.IndexOf("＜＜作者");

                if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                {
                    footerText = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                }

                if (string.IsNullOrEmpty(footerText))
                {
                    startIndex = srcText.IndexOf("辑录＞＞");
                    endIndex = srcText.IndexOf("＜＜辑录");
                    if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                    {
                        footerText = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                    }
                }
            }

            return sb.ToString().Replace("\r", "");
        }

        /// <summary>
        /// 是否任务列表项文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsTaskLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            Regex regex = new Regex(@"^ {0,3}\[[ 　\t]*[\-\+\%\#－＋％＃][ 　\t]*\][^\(].*$");
            var match = regex.Match(lineText);
            return match.Success;
        }

        /// <summary>
        /// 是否以分号开头的注释文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith(";") || tmp.StartsWith("；") || tmp.StartsWith(":") || tmp.StartsWith("：")) return true;

            return false;
        }

        /// <summary>
        /// 是否指示“要编译 Html 菜单”的标记文本。
        /// </summary>
        /// <param name="lineText">文本行。</param>
        public static bool IsMenuMarkText(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            Regex r = new Regex(@"^[   ]{0,3}[;；](\[?(([mMｍＭ][eEｅＥ][nNｎＮ][uUｕＵ])|(菜单))\]?)[:：].*$");
            var match = r.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 是否时间标签文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsDateLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            //Regex r = new Regex(@"^[ 　]{0,3}\[((((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年](0?[13578]|1[02])[-.,，。－、．/月](0?[1-9]|[12]\d|3[01])日{0,1})|(((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年](0?[13456789]|1[012])[-.,，。－、．/月](0?[1-9]|[12]\d|30)日{0,1})|(((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年]0?2[-.,，。－、．/月](0?[1-9]|1\d|2[0-8])日{0,1})|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))[-.,，。－、．/年]0?2-29[-.,，。－、．/月])日{0,1})\]");
            Regex r = new Regex(@"^[ 　]{0,3}\[\d{4,4}[-.,，。－、．/年][01]{0,1}\d[-.,，。－、．/月][0123]{0,1}\d日{0,1}([ 　]{1,}\d{1,2}[:：]\d{1,2}){0,1}\](\[[ 　\t]*[\-\%\+\#－＋％＃][ 　\t]*\]){0,1}");
            var match = r.Match(lineText);
            if (match != null && match.Success)
            {
                var result = GetDateFromDateLine(lineText);
                if (result == null) return false;
                if (result.HasValue == false) return false;

                return true;
            }
            return false;
        }

        /// <summary>
        /// 格式化日期标签文本行的日期标志文本部分。
        /// </summary>
        /// <param name="dateMarkText">日期标志文本。形如：[2016-2-1]之类。</param>
        public static string FormatDateLineMark(string dateMarkText)
        {
            var trimChars = new char[] { ' ', '　', '\t' };
            return dateMarkText.Replace("/", "-").Replace(".", "-").Replace(",", "-").Replace("，", "-")
                .Replace("。", "-").Replace("－", "-").Replace("、", "-").Replace("．", "-")
                .Replace("年", "-").Replace("月", "-").Replace("日", "").Replace("－", "-").Replace("：", ":").Trim(trimChars);
        }

        /// <summary>
        /// 格式化日期文本行。
        /// </summary>
        /// <param name="dateLineText">要格式化的日期文本行。</param>
        public static string FormatDateLine(string dateLineText)
        {
            if (string.IsNullOrWhiteSpace(dateLineText)) return "";
            var replacedLine = dateLineText.Replace("－", "-");
            var index = replacedLine.IndexOf("]");
            if (index < 0) return dateLineText;//原样返回

            string header;
            string tail;
            if (replacedLine.Length >= index + 2)
            {
                char c = replacedLine[index + 1];
                if (c == '-' || c == '－' || c == '%' ||
                    c == '#' || c == '+')
                {
                    header = dateLineText.Substring(0, index + 1) + c;
                    tail = dateLineText.Substring(index + 2);
                }
                else
                {
                    header = dateLineText.Substring(0, index + 1);
                    tail = dateLineText.Substring(index + 1);
                }
            }
            else
            {
                header = dateLineText.Substring(0, index + 1);
                tail = dateLineText.Substring(index + 1);
            }

            var trimChars = new char[] { ' ', '　', '\t' };
            //-.,，。－、．/

            var secHeaderMark = "";
            var tailTrim = tail.Trim(trimChars);
            if (tail.StartsWith("[-]") || tail.StartsWith("[－]") || tail.StartsWith("[%]") ||
                tail.StartsWith("[#]") || tail.StartsWith("[+]"))
            {
                secHeaderMark = tail.Substring(0, 3).Replace("[－]", "[-]");
                tail = tailTrim.Substring(3);
            }

            return " " + FormatDateLineMark(header) + secHeaderMark + " " + tail.Trim(trimChars);

        }

        /// <summary>
        /// 从日期文本行中取日期标志文本部分。
        /// </summary>
        /// <param name="dateLine">日期文本行。</param>
        public static DateTime? GetDateFromDateLine(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return null;
            var index = dateLine.IndexOf("]");

            DateTime dt;
            if (index < 0)
            {
                if (DateTime.TryParse(dateLine, out dt)) return dt;

                return null;//原样返回
            }

            var header = dateLine.Substring(0, index + 1).Replace("[", "").Replace("]", "");

            if (DateTime.TryParse(header, out dt)) return dt;

            return null;
        }

        /// <summary>
        /// 取时间文本中的任务完成状态标记文本。
        /// </summary>
        /// <param name="dateLine">时间文本。</param>
        public static string GetDateLineStatusMark(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return "";

            Regex regex = new Regex(@"\]\[[ 　\t]*[-－+#%][ 　\t]*\]");
            var match = regex.Match(dateLine);
            if (match != null && match.Success)
            {
                return dateLine.Substring(match.Index + 1, match.Length - 1).Replace(" ", "").Replace("　", "").Replace("\t", "");
            }

            return "";
        }

        /// <summary>
        /// 取时间文本行中的内容文本（表示该时间标签指代的时间发生的事件的具体意义，而非时间标签所指代的时间）。
        /// </summary>
        /// <param name="dateLine">时间文本。</param>
        public static string GetContentTextOfDateLine(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return "";

            Regex regex = new Regex(@"\[[ 　\t]{0,}[-－%#+][ 　\t]{0,}\][ 　\t].*$");
            var match = regex.Match(dateLine);
            if (match != null && match.Success)
            {
                var index = match.Value.IndexOf("]");
                if (index < 0) return match.Value;
                return match.Value.Substring(index + 1).Trim(new char[] { ' ', '　', '\t' });
            }
            return "";
        }

        /// <summary>
        /// 是否以冒号开头的注释。这种注释会被编译进 Html 文档，并在 Html 页面上呈现特殊效果。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsCompileCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith(":") || tmp.StartsWith("：")) return true;

            return false;
        }

        /// <summary>
        /// 取文档标题文本。
        /// </summary>
        /// <param name="lineText">以 % 开头的文本行。</param>
        /// <returns>返回去除 % 标记的其它文本。</returns>
        public static string GetDocumentTitle(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return null;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return null;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (lineText.StartsWith("%")) return tmp.Substring(1);

            var tmp2 = tmp.Replace("Ｔ", "t").Replace("ｔ", "t").Replace("Ｉ", "i").Replace("ｉ", "i").Replace("Ｌ", "l").Replace("ｌ", "l").Replace("Ｅ", "e").Replace("ｅ", "e")
                .Replace("〉", ">").ToLower();
            if (tmp2.StartsWith("title>")) return tmp.Substring(6);

            if (tmp.StartsWith("标题>") || tmp.StartsWith("标题〉")) return tmp.Substring(3);

            return null;
        }

        /// <summary>
        /// 判断文本行是否页脚文本。（页脚文本以 @ 或 · 开头。）
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns></returns>
        public static bool IsPageFoot(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return false;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith("@") || tmp.StartsWith("·")) return true;

            return false;
        }

        /// <summary>
        /// 判断文本行是否页眉文本。（页眉文本以波形符~开头。）
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns>返回去除波形符以后的其余文本。</returns>
        public static bool IsPageHeader(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return false;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith("~")) return true;

            return false;
        }

        /// <summary>
        /// 判断路径指向的 Markdown 文档是否被加密。
        /// </summary>
        /// <param name="fullFilePath">Markdown 文件路径。</param>
        private static bool IsEncripied(string fullFilePath)
        {
            if (String.IsNullOrWhiteSpace(fullFilePath) || File.Exists(fullFilePath) == false) return false;

            System.IO.StreamReader s = File.OpenText(fullFilePath);
            var fstEncriptLine = s.ReadLine();
            if (string.IsNullOrEmpty(fstEncriptLine)) return false;

            var fstDecriptedLine = SetPasswordPanel.TextDecrypt(fstEncriptLine.Replace("[[<r>]]", "\r").Replace("[[<n>]]", "\n").ToCharArray(), "DyBj#PpBb");
            if (fstDecriptedLine.Contains("Password:") && fstDecriptedLine.Contains("Question:") && fstDecriptedLine.Contains("|"))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 用于决定按何种方式将 Markdown 切分为幻灯片。
        /// </summary>
        public enum PresentateHtmlSplitterType
        {
            /// <summary>
            /// 只演示自定义折叠区中的内容，其它忽略。
            /// </summary>
            Region,
            /// <summary>
            /// 演示所有一级标题下属的内容，但不包括第一个一级标题之前的内容。
            /// 这样设计的原因在于：第一个一级标题前可能只有文档标题而没有其它内容——这时候会显示为一个空页面。
            /// </summary>
            TopLevelHeader,
            /// <summary>
            /// 演示时按水平线分割。
            /// </summary>
            HorizontalLine,
            /// <summary>
            /// 按照文档中定义的方式来，方式应为下列三种之一。
            /// 如果文档中没有定义，则默认按一级标题（TopLevelHeader）。
            /// </summary>
            ByDocument,
        }

        /// <summary>
        /// 将一个 Markdown 文件按水平线拆分成多个文件，再编译。
        /// 这是为了演示。
        /// </summary>
        /// <param name="srcFilePath"></param>
        /// <param name="htmlHeadersCollpase"></param>
        /// <param name="htmlTitle"></param>
        /// <returns></returns>
        public static List<Uri> Compile(string srcFilePath,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            PresentateHtmlSplitterType presentateHtmlSplitterType,
            ref List<string> htmlFilesList, ref List<string> mdFilesList,
            bool onlyPresentateHeaders = false)
        {
            List<Uri> uris = new List<Uri>();
            htmlFilesList.Clear();
            mdFilesList.Clear();

            if (File.Exists(srcFilePath) == false) return uris;

            var sourceText = File.ReadAllText(srcFilePath);

            switch (presentateHtmlSplitterType)
            {
                case PresentateHtmlSplitterType.Region:
                    {
                        SplitAndCompileByRegions(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList);
                        break;
                    }
                case PresentateHtmlSplitterType.TopLevelHeader:
                    {
                        SplitAndCompileByTopLevelHeaders(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList, onlyPresentateHeaders);
                        break;
                    }
                case PresentateHtmlSplitterType.HorizontalLine:
                default:
                    {
                        SplitAndCompileByHorizontalLine(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList);
                        break;
                    }
            }
            return uris;
        }

        public static void SplitAndCompileByHorizontalLine(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<Uri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList)
        {
            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);

            List<string> pageTexts = new List<string>();

            StringBuilder sb = new StringBuilder();
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null && htmlTitle == "")
                {
                    htmlTitle = title;
                    continue;
                }

                if (IsHorizontalLineText(line))
                {
                    pageTexts.Add(sb.ToString());
                    sb = new StringBuilder();
                    sb.Append("\r\n");
                    continue;
                }

                sb.Append(line);
                sb.Append("\r\n");
            }

            pageTexts.Add(sb.ToString());

            for (int i = 0; i < pageTexts.Count; i++)
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + pageTexts[i];
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = Compile(pageFileName, htmlHeadersCollpase,
                    PresentateHtmlSplitterType.HorizontalLine,
                    i + 1, ref htmlTitle, false, true, 1.5f);

                mdFilesList.Add(pageFileName);
                htmlFilesList.Add(newHtmlFilePath);

                uris.Add(new Uri("file:///" + newHtmlFilePath));
            }
        }

        public static void SplitAndCompileByTopLevelHeaders(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<Uri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList,
            bool onlyPresentateHeaders)
        {
            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);

            List<string> pageTexts = new List<string>();

            StringBuilder sb = new StringBuilder();
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null && htmlTitle == "")
                {
                    htmlTitle = title;
                    continue;
                }

                Regex regexTopLevelHeader = new Regex(@"^[ 　]{0,3}[#＃][^#＃]*$");
                var match = regexTopLevelHeader.Match(line);

                if (match != null && match.Success)
                {
                    pageTexts.Add(sb.ToString());
                    sb = new StringBuilder();
                    sb.Append(line);
                    sb.Append("\r\n");
                    continue;
                }

                if (onlyPresentateHeaders)
                {
                    Regex regexHeader = new Regex(@"^[ 　]{0,3}[#＃].*$");
                    var matchHeader = regexHeader.Match(line);
                    if (matchHeader != null && matchHeader.Success)
                    {
                        sb.Append(line);
                        sb.Append("\r\n");
                    }
                }
                else
                {
                    sb.Append(line);
                    sb.Append("\r\n");
                }
            }

            pageTexts.Add(sb.ToString());

            for (int i = 1; i < pageTexts.Count; i++)//从1开始，不显示第一个一级标题前面的内容。
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + pageTexts[i];
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = Compile(pageFileName, htmlHeadersCollpase,
                     PresentateHtmlSplitterType.TopLevelHeader, i, ref htmlTitle, false, true, 1.5f);

                mdFilesList.Add(pageFileName);
                htmlFilesList.Add(newHtmlFilePath);

                uris.Add(new Uri("file:///" + newHtmlFilePath));
            }
        }

        public static void SplitAndCompileByRegions(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<Uri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList)
        {
            //★注意：C# 正则表达式在多行查询时，要匹配包含换行符在内的所有字符，必须使用([\s\S]*)表示。
            //        加上?是表示非贪婪模式。
            Regex regionRegex = new Regex(@"(?<=(^[ 　]{0,3}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]*[ｉｗｅｑiweq]?[ 　\t]*\{).*$)[\s\S]*?(?=(^[ 　]{0,3}\}[ 　\t]*[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ].*$))", RegexOptions.Multiline);
            var matches = regionRegex.Matches(sourceText);

            if (matches == null || matches.Count <= 0)
            {
                return;
            }

            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null)
                {
                    htmlTitle = title;
                    break;
                }
            }

            for (int i = 0; i < matches.Count; i++)
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + matches[i].Value;
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = Compile(pageFileName, htmlHeadersCollpase,
                     PresentateHtmlSplitterType.Region, i + 1, ref htmlTitle, false, true, 1.5f);

                mdFilesList.Add(pageFileName);
                htmlFilesList.Add(newHtmlFilePath);

                uris.Add(new Uri("file:///" + newHtmlFilePath));
            }
        }

        /// <summary>
        /// 将 Markdown 文件编译为 Html 网页文件。基本流程如下：
        ///     1.对 Markdown 文件中的文本进行格式化；
        ///     2.移除不必要编译进 Html 中的行；
        ///     3.对 Markdown 进行预处理，将一些自定义的 Markdown 元素先编译为 Html 标签；
        ///     4.调用 MarkdownSharp 对经过预处理的 Markdown 文档进行编译；
        ///     5.对 MarkdownSharp 编译后的 Html 文本进行追加处理，使之支持六级标题等功能；
        ///     6.根据偏好设置决定是否对追加处理后的 Html 文本进行格式化。
        /// </summary>
        /// <param name="srcFilePath">要编译的 Markdown 源文件的路径。</param>
        /// <param name="htmlHeadersCollpase">在编译好的 Html 文档中六级标题是否支持折叠功能。</param>
        /// <param name="presentateHtmlSplitterType">编译整个文档时应传入 null。</param>
        /// <param name="startH1Number">此参数用以指定&lt;H1&gt;的编号从什么值开始，通常应传入1。
        /// 此参数是为了编译切块的幻灯片 Html 文档而存在的。</param>
        /// <param name="htmlTitle">编译好的 Html 文档应以什么文本作为标题。此标题应由 Markdown 文件内部设置的“%xxx”行决定。</param>
        /// <param name="withoutAdonors">不向页面添加标题、脚注、编译时间等元素。</param>
        /// <returns>如果编译成功，返回新文件路径。</returns>
        public static string Compile(string srcFilePath, MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
              PresentateHtmlSplitterType? presentateHtmlSplitterType, int startH1Number,
              ref string htmlTitle, bool addBackToIndexLink, bool withoutAdonors = false, float webBrowserZoom = 1)
        {
            string shortFileName;
            int lastIndex = srcFilePath.LastIndexOf("\\");
            if (lastIndex >= 0)
            {
                string name = srcFilePath.Substring(lastIndex + 1);
                if (string.IsNullOrEmpty(name))
                {
                    shortFileName = "unnamed.md";
                }
                else shortFileName = name;
            }
            else
            {
                shortFileName = "unnamed.md";
            }

            string htmlDocumentTitle;
            string htmlDocumentPageHeader;
            string footerText;

            MarkdownSharp.Markdown md = new MarkdownSharp.Markdown();

            string contentText;

            bool isEncrypted = false;
            string password, passwordTip;
            isEncrypted = IsFileEncrypted(srcFilePath, out password, out passwordTip);

            if (isEncrypted)
            {
                string result = null;
                foreach (UIElement ue in Globals.MainWindow.mainTabControl.Items)
                {
                    var edit = ue as MarkdownEditor;
                    if (edit != null && string.IsNullOrEmpty(edit.FullFilePath) == false &&
                        edit.FullFilePath.ToLower() == srcFilePath.ToLower())
                    {
                        if (edit.InputPasswordPanel.Visibility == Visibility.Collapsed)
                        {
                            result = edit.Password;
                        }
                    }
                }

                if (result == null)
                {
                    result = InputBox.Show(Globals.AppName, $"{passwordTip}", "", false, "注意：密码区分大小写。", true);
                }

                if (result == null)
                {
                    return "";
                }
                else
                {
                    if (result == password)
                    {
                        var allText = File.ReadAllText(srcFilePath);
                        contentText = SetPasswordPanel.TextDecrypt(allText.Substring(allText.IndexOf("\r\n") + 2).ToCharArray(), "DyBj#PpBb");
                    }
                    else
                    {
                        LMessageBox.Show("密码错误，无法编译！", Globals.AppName,
                            MessageBoxButton.OK, MessageBoxImage.Warning);
                        return null;
                    }
                }
            }
            else
            {
                contentText = File.ReadAllText(srcFilePath);
            }

            bool compilePageMenu;
            string htmlBody = RemoveExtraLines(contentText, out htmlDocumentTitle, out htmlDocumentPageHeader, out footerText, out compilePageMenu);

            if (Globals.MainWindow.CompilePageMenu)
                compilePageMenu = true;     //强制编译出页面菜单

            if (withoutAdonors)
                compilePageMenu = false;    //演示模式下，不添加此左栏菜单。

            bool forbiddenAutoNumber = true;

            if (presentateHtmlSplitterType != null)
            {
                if (presentateHtmlSplitterType.Value == PresentateHtmlSplitterType.TopLevelHeader)
                {
                    //当切分处理一个 Markdown 文件时，只有使用一级标题作为分割标准才能保证序号不出错。
                    //所以其它两种分割情况下，都必须
                    forbiddenAutoNumber = false;
                }
            }
            else forbiddenAutoNumber = false;//如果未传入这个值，说明不是在切分编译。

            htmlBody = CustomMarkdownSupport.PreProcessor(htmlBody, startH1Number, forbiddenAutoNumber, new char[] { '\n' });

            if (string.IsNullOrEmpty(htmlDocumentTitle))
            {
                htmlDocumentTitle = "Markdown Document";
            }

            htmlBody = md.Transform(htmlBody);

            string directoryLevelMark = GetDirectoryLevelMark(srcFilePath);

            var removeEmptyParagraphsRegex = new Regex(@"[ 　\t]*<p>[ 　\t]*</p>[ 　\t]*");
            htmlBody = removeEmptyParagraphsRegex.Replace(htmlBody, "");

            htmlBody = CustomMarkdownSupport.AppendProcessor(htmlBody, directoryLevelMark,
                htmlHeadersCollpase, Globals.MainWindow.CompileCodeToFillBlank, compilePageMenu,
                (addBackToIndexLink ? srcFilePath : null));
            //htmlBody = htmlBody.Replace("<p><div ", "<div ").Replace("</div></p>", "</div>");//去除不必要的空段。

            //去除不必要的空段。
            htmlBody = Regex.Replace(htmlBody,
                @"<[pP]>[ 　\t]*((<[iI][mM][gG].*/>)|(<[dD][iI][vV]>*.</[dD][iI][vV]>))[ 　\t]*</[pP]>",
                new MatchEvaluator(FormatParagraphsTag));

            if (string.IsNullOrEmpty(htmlBody) == false)
            {
                string cssThemePrefix = (withoutAdonors ? "presentation_" : "lesson_");

                String linkCss2 = "<link id='themeLink' rel=\"stylesheet\" href=\"" + directoryLevelMark
                    + cssThemePrefix + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\">";

                String linkCss3 = "<link rel=\"stylesheet\" href=\"" + directoryLevelMark
                    + "menu_" + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\" />";

                string script = "<script>"
                           + "function ShowPage() "
                           + "{"
                           + "document.getElementById('content').style.visibility = \"visible\";"
                           + $"document.body.style.zoom = {webBrowserZoom};"
                           + "}"
                           + "function HidePage()"
                           + "{"
                           + "document.getElementById('content').style.visibility = \"hidden\";"
                           + "}"
                           + "</script>";

                string timeText = "";
                if (Globals.MainWindow.AppendTimeOfCompiling)
                {
                    timeText = "<p id =\"compile_time\">" + DateTime.Now.ToString() + "</p>";
                }

                string pagefooterString = "<div class=\"foot\"><p id=\"author\">" + footerText //这是文档的脚注文本，一般可以填组织、作者名称什么的。
                    + $"</p>{timeText}</div>";

                var charset = Globals.MainWindow.DefaultEncoding;//"gb2312";//"utf-8";

                //解决总是浏览网页时总是显示“为保护……你的 Web 浏览器已经限制此文件……”警示信息条。
                //<!-- saved from url=(0014)about:internet -->必须在Html之后Head之前，否则Microsoft Edge中无法正确显示。
                string htmlDocumentText = "<!DOCTYPE html>\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
                                //+ "<!-- saved from url=(0014)about:internet -->\n"
                                + "<!-- saved from url=(0016)http://localhost -->\r\n"
                                + "<head>"
                                + "<title>"
                                + htmlDocumentTitle
                                + "</title>\n"
                                + "<meta name=\"info\" content=\""
                                + shortFileName
                                + "\" >"
                                + "<meta name=\"viewport\" content=\"width=device-width, user-scalable=yes\" />"
                                + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + charset + "\">"
                                + linkCss2
                                + (compilePageMenu ? linkCss3 : "")
                                + script
                                //+ "</head><body onLoad=\"HidePage()\">"
                                + "</head><body" + (withoutAdonors ? "scroll=\"no\" " : "") + " onLoad=\"ShowPage()\">"
                                + "<p style=\"visibility: collapse;\"><span id=\"__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\" class=\"anchor\"></span></p>"       //这个锚总是存在的。
                                + (withoutAdonors ? "" : "<div style=\"text-align:right\"><span class=\"pageheader\">")
                                + (withoutAdonors ? "" : htmlDocumentPageHeader)
                                + (withoutAdonors ? "" : "</span><a onclick=\"print()\" style=\"visibility: hidden\">打印〔此功能会丢失内容〕...</a></div>")
                                + "<div id=\"printArea\">"
                                + (withoutAdonors ? "" : "<p class='fileheader' id='file_header'>")
                                + (withoutAdonors ? "" : htmlDocumentTitle)
                                //这是整个文档的标题，以%开头。
                                + (withoutAdonors ? "" : "</p><hr />")
                                + "<div id=\"content\">"
                                + htmlBody
                                + "<script></script>"
                                + (compilePageMenu ? "<p class=\"back_to_top_link\"><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>" : "")
                                + "</div>"
                                + (withoutAdonors ? "" : "<br /><br /><br /><br /><br /><hr />")
                                + (withoutAdonors ? "" : pagefooterString)
                                + (addBackToIndexLink ? BuildIndexLink(srcFilePath) : "")
                                + "</div>"
                                + "</body></html>";

                htmlTitle = htmlDocumentTitle;

                //对编译后的html格式化。
                if (Globals.MainWindow.FormatAfterCompile)
                {
                    NSoup.Nodes.Document doc = NSoup.NSoupClient.Parse(htmlDocumentText);
                    htmlDocumentText = doc.OuterHtml();
                }

                var newFileName = srcFilePath.Substring(0, srcFilePath.Length - 3) + ".html";

                if (charset == "utf-8")
                {
                    using (StreamWriter stream = new StreamWriter(newFileName, false, Encoding.UTF8))
                    {
                        stream.Write(htmlDocumentText);
                    }
                }
                else if (charset == "gb2312")
                {
                    using (StreamWriter stream = new StreamWriter(newFileName, false, System.Text.Encoding.GetEncoding("gb2312")))
                    {
                        stream.Write(htmlDocumentText);
                    }
                }

                return newFileName;
            }

            return null;
        }

        /// <summary>
        /// 如果段落中只有一个 Img 标签，则将此 Imag 标签从段落中独立出来。这样可以实现图像本身的对齐效果。
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public static string FormatParagraphsTag(Match match)
        {
            if (match.Length < 7) return match.Value;
            return match.Value.Substring(3, match.Length - 7);
        }

        private static object BuildIndexLink(string srcFilePath)
        {
            if (srcFilePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
            {
                var tail = srcFilePath.Substring(Globals.PathOfWorkspace.Length);
                if (tail.StartsWith("\\") || tail.StartsWith("/"))
                {
                    tail = tail.Substring(1);
                }

                var spans = tail.Split(new char[] { '\\', '/' }, StringSplitOptions.None);

                StringBuilder sb = new StringBuilder();
                for (int i = 1; i < spans.Length; i++)//注意i从1开始，不然目录会多一层。
                {
                    sb.Append("../");
                }

                return $"<a href=\"{sb.ToString()}_index.html\">回到目录页</a>";
            }

            return "";
        }

        /// <summary>
        /// 判断指定文件是否被加密。
        /// </summary>
        /// <param name="srcFilePath">Markdown 文件的路径。</param>
        /// <param name="password">用以传出加密文件的密码。</param>
        /// <param name="passwordTip">用以传出加密文件的密码提示信息。</param>
        /// <returns></returns>
        public static bool IsFileEncrypted(string srcFilePath, out string password, out string passwordTip)
        {
            using (var sr = new StreamReader(srcFilePath))
            {
                var fstLine = sr.ReadLine();

                if (string.IsNullOrWhiteSpace(fstLine))
                {
                    passwordTip = password = "";
                    return false;
                }

                var decryptedFstLine = SetPasswordPanel.TextDecrypt(
                    fstLine.Replace("[[<r>]]", "\r").Replace("[[<n>]]", "\n").ToCharArray(), "DyBj#PpBb");
                var indexOfPassword = decryptedFstLine.IndexOf("Password:");
                var indexOfQuestion = decryptedFstLine.IndexOf("Question:");

                password = "";
                passwordTip = "";
                if (indexOfQuestion >= 0 && indexOfPassword >= 10)
                {
                    password = decryptedFstLine.Substring(indexOfPassword + 9);
                    passwordTip = decryptedFstLine.Substring(9, indexOfPassword - 10);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 取当前路径相对于当前工作区路径的层级差。
        /// </summary>
        /// <param name="fullFilePath">文件路径。</param>
        public static int GetSubLayerOfDirectory(string fullFilePath)
        {
            if (string.IsNullOrEmpty(fullFilePath)) return -1;

            if (fullFilePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false) return -1;

            var tailTextOfPath = fullFilePath.Substring(Globals.PathOfWorkspace.Length);

            int i = 0;
            foreach (char c in tailTextOfPath)
            {
                if (c == '\\')
                {
                    i++;
                }
            }
            return i;
        }

        /// <summary>
        /// 根据文件路径与当前工作区路径之间的层级差来生成相对引用字符串前缀。
        /// </summary>
        /// <param name="fullFilePath">文件路径。</param>
        public static string GetDirectoryLevelMark(string fullFilePath)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i <= GetSubLayerOfDirectory(fullFilePath) - 1; i++)
            {
                sb.Append("../");
            }
            return sb.ToString();
        }

    }

    /// <summary>
    /// 此类用于在编译处理 <h1></h1>...<h6></h6>，以便使其这些标题支持折叠、自动编号等功能。
    /// </summary>
    public class HtmlHeaderInfo
    {
        /// <summary>
        /// 此标题所需要的 JavaScript。
        /// </summary>
        public string JavaScriptText { get; set; } = "";

        /// <summary>
        /// 此标题之前所有标题对应 Div 的结尾字符串。因为前面可能有几层，所以可能像这样：“</div></div></div>”。
        /// </summary>
        public string PreviewHeaderPanelCloseDivsMark { get; set; } = "";

        /// <summary>
        /// 转换后的文本。
        /// </summary>
        public string NewText { get; set; } = "";

        /// <summary>
        /// 此标题是否已关闭。
        /// </summary>
        public bool IsClosed { get; set; } = false;

        /// <summary>
        /// 标题文本。
        /// </summary>
        public string Header { get; set; } = "";

        /// <summary>
        /// Html文本。
        /// </summary>
        public string Html { get; set; } = "";

        /// <summary>
        /// 标题层级。
        /// </summary>
        public int Level { get; set; } = 0;

        /// <summary>
        /// 从 Html 在提取出来的正则匹配信息。
        /// </summary>
        public Match Match { get; set; } = null;
    }
}
